From b544d5cccd76735e034c8055918a50927b437474 Mon Sep 17 00:00:00 2001
From: wenxiangchao <wenxiangchao@ihengxin.com.cn>
Date: 星期二, 10 十一月 2026 08:29:04 +0800
Subject: [PATCH] fix: 维护模式重复进入失败与按钮灰死 + Broker 并发验证用例

---
 Plugin.IntegrationTest/MainWindow.xaml.cs |  476 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 450 insertions(+), 26 deletions(-)

diff --git a/Plugin.IntegrationTest/MainWindow.xaml.cs b/Plugin.IntegrationTest/MainWindow.xaml.cs
index 3e80e2d..ea96761 100644
--- a/Plugin.IntegrationTest/MainWindow.xaml.cs
+++ b/Plugin.IntegrationTest/MainWindow.xaml.cs
@@ -8,8 +8,12 @@
 using System.Windows;
 using System.Windows.Media;
 using System.Windows.Threading;
+using Aging.Infrastructure;
+using Aging.Infrastructure.Contracts;
 using Aging.Infrastructure.Drivers;
+using Aging.Infrastructure.Services;
 using CanSimulator;
+using HMI.Core.Util;
 using log4net;
 using Plugin.DeviceConfig.Entity;
 using Plugin.DeviceConfig.Model;
@@ -38,6 +42,14 @@
         private CanBusSimulator? _canBus;
         private CanDebugPanel? _canDebugPanel;
 
+        // V3.0锛欴eviceBroker 瀹夸富瀛愯繘绋嬶紙瀛樺湪 Broker 璁惧鏃惰嚜鍔ㄦ媺璧凤級
+        private System.Diagnostics.Process? _brokerProcess;
+
+        // 鑷姩鍖栨祴璇曟湇鍔�
+        private IAutoDeviceFacade? _autoFacade;
+        private ICanBusService? _canBusService;
+        private CancellationTokenSource? _autoCts;
+
         public MainWindow()
         {
             InitializeComponent();
@@ -51,9 +63,22 @@
 
         private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
         {
-            // 娓呯悊 CAN 妯℃嫙鍣�
-            _canBus?.Dispose();
-            CanDriverAdapter.ResetAllBuses();
+            // 鍙栨秷姝e湪杩愯鐨勮嚜鍔ㄥ寲娴嬭瘯
+            try { _autoCts?.Cancel(); } catch { }
+
+            // 鍋滄 DeviceBroker 瀹夸富锛堝叾鎸佹湁鐨勭墿鐞嗕細璇濋殢杩涚▼閫�鍑洪噴鏀撅紝绉熺害闈� TTL 杩囨湡+浜哄伐鍥炴敹锛�
+            try
+            {
+                if (_brokerProcess != null && !_brokerProcess.HasExited)
+                {
+                    _brokerProcess.Kill();
+                    _brokerProcess.WaitForExit(3000);
+                }
+                _brokerProcess?.Dispose();
+            }
+            catch { }
+
+            // CAN 妯℃嫙鍣ㄥ凡澶栭儴鍖栦负鐙珛鏈嶅姟杩涚▼锛屾湰杩涚▼鏃犺繘绋嬪唴鎬荤嚎闇�娓呯悊
 
             // 涓嶅垹闄ゆ祴璇曠洰褰曪紝淇濈暀鏁版嵁浠ヤ究閲嶅惎鍚庢仮澶�
             // 锛堝闇�鎵嬪姩閲嶇疆锛屼娇鐢ㄥ伐鍏锋爮涓殑 "閲嶇疆鏁版嵁" 鎸夐挳锛�
@@ -74,22 +99,26 @@
 
             try
             {
-                // 鍒濆鍖栦华鍣ㄤ华琛ㄦ敞鍐屾彃浠�
+                // 鍒濆鍖栦华鍣ㄤ华琛ㄦ敞鍐屾彃浠讹紙寤哄簱 data.db锛�
                 _deviceConfigPlugin = new Plugin.DeviceConfig.DeviceConfig();
                 _deviceConfigPlugin.SetBaseFolder(_testDir);
                 _deviceConfigPlugin.SetLogger(_testLog);
                 _deviceConfigPlugin.InitFinish();
                 deviceConfigHost.Child = _deviceConfigPlugin;
 
-                // 鍒濆鍖栫‖浠惰皟璇曟彃浠�
+                // 棰勭疆 CAN 娴嬭瘯璁惧锛堝繀椤诲湪鍩虹璁炬柦鍒濆鍖栧墠鍐欏叆锛孊ootstrapper 鎹娉ㄥ唽浠g悊椹卞姩锛�
+                SeedPresetCanDevices();
+
+                // 鍒濆鍖栧熀纭�璁炬柦 & 鑷姩鍖栨湇鍔★紙蹇呴』鍦� DeviceDebug.InitFinish 涔嬪墠锛�
+                // 瀹冧粠 UtilContainer 鎷夊彇 DeviceManager/ResourceManager锛屾敞鍐屽湪 Bootstrapper.Initialize 瀹屾垚锛�
+                InitializeAutoServices();
+
+                // 鍒濆鍖栫‖浠惰皟璇曟彃浠讹紙姝ゆ椂鍩虹璁炬柦鏈嶅姟宸插氨缁紝缁存姢绉熺害/椹卞姩澶嶇敤閾捐矾鍙敤锛�
                 _deviceDebugPlugin = new Plugin.DeviceDebug.DeviceDebug();
                 _deviceDebugPlugin.SetBaseFolder(_testDir);
                 _deviceDebugPlugin.SetLogger(_testLog);
                 _deviceDebugPlugin.InitFinish();
                 deviceDebugHost.Child = _deviceDebugPlugin;
-
-                // 棰勭疆 CAN 娴嬭瘯璁惧锛堟柟渚跨洿鎺ラ獙璇侀┍鍔ㄤ笌闈㈡澘锛�
-                SeedPresetCanDevices();
 
                 // 鍒锋柊鎻掍欢鍐呴儴鍒楄〃锛堥渶鎵嬪姩璋冪敤鎻掍欢鏂规硶锛�
                 _deviceConfigPlugin.ForceRefreshForTest();
@@ -107,7 +136,8 @@
                 Log("  2. 宸查缃� CAN-001 (CAN_BUS_1:0) 鍜� CAN-002 (CAN_BUS_1:1) 渚涙祴璇�");
                 Log("  3. 鍒囨崲鍒� '纭欢璋冭瘯' Tab锛岄�夋嫨 CAN-001 浼氳嚜鍔ㄦ墦寮� CAN 闈㈡澘骞惰繛鎺ユ�荤嚎");
                 Log("  4. 鍒囨崲鍒� 'CAN 妯℃嫙鍣�' Tab 鍙墜鍔ㄦ帶鍒舵�荤嚎鎴栧彟涓�涓妭鐐瑰彂閫佹暟鎹�");
-                Log("  5. 鍙殢鏃剁偣鍑� '杩愯鑷姩鍖栨祴璇�' 楠岃瘉鏍稿績閫昏緫");
+                Log("  5. 鍒囨崲鍒� '鍏ㄨ嚜鍔ㄦ祴璇�' Tab 鍙綋楠屾柟妗� B+C 鐨勮嚜鍔ㄥ寲姝ラ");
+                Log("  6. 鍙殢鏃剁偣鍑� '杩愯鑷姩鍖栨祴璇�' 楠岃瘉鏍稿績閫昏緫");
             }
             catch (Exception ex)
             {
@@ -628,6 +658,409 @@
             }
         }
 
+        #region 鍏ㄨ嚜鍔ㄦ祴璇曪紙鏂规 B+C锛�
+
+        /// <summary>
+        /// 鍒濆鍖栬嚜鍔ㄥ寲鏈嶅姟锛�
+        ///   1) 鍚姩 Infrastructure Bootstrapper锛圧esourceManager/DeviceManager/DeviceService/AutoDeviceFacade/CanBusService锛�
+        ///   2) 涓洪缃� CAN 璁惧娉ㄥ唽椹卞姩
+        ///   3) 灏嗚澶囧垪琛ㄥ~鍒� UI 涓嬫媺妗�
+        /// </summary>
+        private void InitializeAutoServices()
+        {
+            try
+            {
+                // 鍒濆鍖栧熀纭�璁炬柦锛堝箓绛夛紝宸插垵濮嬪寲鏃惰嚜鍔ㄨ烦杩囷級
+                string dbPath = Path.Combine(_testDir, "data.db");
+                try { InfrastructureBootstrapper.Initialize(dbPath); }
+                catch (Exception ex) { Log($"[WARN] Bootstrapper 鍒濆鍖栧紓甯�: {ex.Message}"); }
+
+                // 浠� UtilContainer 鎷夊彇鏈嶅姟瀹炰緥
+                var container = new UtilContainer();
+                _autoFacade = container.GetUtil<IAutoDeviceFacade>("AutoDeviceFacade");
+                _canBusService = container.GetUtil<ICanBusService>("CanBusService");
+
+                // 涓洪缃澶囨敞鍐岄┍鍔紙鑷姩鍚姩 CAN 鎬荤嚎锛�
+                RegisterCanDriver("CAN-001", "CAN_BUS_1:0");
+                RegisterCanDriver("CAN-002", "CAN_BUS_1:1");
+
+                // 鍒濆鍖栬澶囦笅鎷夊垪琛�
+                autoTxDeviceCombo.ItemsSource = new[] { "CAN-001", "CAN-002" };
+                autoRxDeviceCombo.ItemsSource = new[] { "CAN-002", "CAN-001" };
+                autoTxDeviceCombo.SelectedIndex = 0;
+                autoRxDeviceCombo.SelectedIndex = 0;
+
+                if (_autoFacade != null && _canBusService != null)
+                {
+                    Log("[INFO] AutoDeviceFacade 鍜� CanBusService 宸插氨缁�");
+                    Log("[INFO] CAN-001 / CAN-002 椹卞姩宸叉敞鍐�");
+                }
+                else
+                {
+                    Log("[ERROR] 鑷姩鍖栨湇鍔℃湭姝g‘鍒濆鍖栵紝鍏ㄨ嚜鍔ㄦ祴璇� Tab 涓嶅彲鐢�");
+                    autoRunBtn.IsEnabled = false;
+                }
+
+                // V3.0锛氳嫢閰嶇疆搴撳瓨鍦� Broker 鍩熻澶囷紝鑷姩鎷夎捣 DeviceBroker.exe 瀹夸富
+                TryStartDeviceBroker();
+            }
+            catch (Exception ex)
+            {
+                Log($"[ERROR] 鍒濆鍖栬嚜鍔ㄥ寲鏈嶅姟澶辫触: {ex.Message}");
+                Log(ex.StackTrace ?? "");
+                autoRunBtn.IsEnabled = false;
+            }
+        }
+
+        /// <summary>
+        /// V3.0锛氳嫢 data.db 涓瓨鍦� ExecutionMode=Broker 鐨勮澶囷紝鑷姩鎷夎捣 DeviceBroker.exe 瀹夸富瀛愯繘绋嬨��
+        /// 瀹夸富榛樿鍙傛暟鎸囧悜鏈幆澧冨悓涓� data.db/arb.db锛堝悓鏈轰徊瑁佸煙锛夛紱鎵句笉鍒板彲鎵ц鏂囦欢鏃堕檷绾т负璀﹀憡锛�
+        /// Broker 璁惧灏嗘姤 BROKER_UNREACHABLE锛孡ocal 璁惧涓嶅彈褰卞搷銆�
+        /// </summary>
+        private void TryStartDeviceBroker()
+        {
+            try
+            {
+                // 妫�鏌ユ槸鍚﹀凡鏈� DeviceBroker 杩涚▼鍦ㄨ繍琛岋紙閬垮厤澶氬疄渚嬩簤鎶㈢閬�/arb.db锛�
+                if (System.Diagnostics.Process.GetProcessesByName("DeviceBroker").Length > 0)
+                {
+                    Log("[INFO] DeviceBroker 瀹夸富宸插湪杩愯锛岃烦杩囧惎鍔�");
+                    return;
+                }
+
+                var model = new DeviceConfigModel();
+                string dbPath = Path.Combine(_testDir, "data.db");
+                if (!File.Exists(dbPath)) return;
+                model.InitDatabase(dbPath);
+                var brokerDevices = model.GetAllDevices()
+                    .Where(d => d.ExecutionMode == Plugin.DeviceConfig.Entity.ExecutionModeKind.Broker)
+                    .ToList();
+                if (brokerDevices.Count == 0)
+                {
+                    Log("[INFO] 鏃� Broker 鍩熻澶囷紝鏈惎鍔� DeviceBroker 瀹夸富");
+                    return;
+                }
+
+                string exe = FindDeviceBrokerExe();
+                if (exe == null)
+                {
+                    Log("[WARN] 鏈壘鍒� DeviceBroker.exe锛孊roker 璁惧閾捐矾涓嶅彲鐢紙璇锋瀯寤� DeviceBroker 宸ョ▼锛�");
+                    return;
+                }
+
+                var psi = new System.Diagnostics.ProcessStartInfo(exe)
+                {
+                    Arguments = $"--db \"{dbPath}\" --arb \"{Path.Combine(_testDir, "arb.db")}\" --broker broker-b01",
+                    UseShellExecute = false,
+                    CreateNoWindow = true,
+                };
+                _brokerProcess = System.Diagnostics.Process.Start(psi);
+                Log($"[INFO] DeviceBroker 瀹夸富宸插惎鍔� (PID={_brokerProcess?.Id})锛孊roker 璁惧: " +
+                    string.Join(", ", brokerDevices.Select(d => d.DeviceId)));
+            }
+            catch (Exception ex)
+            {
+                Log($"[WARN] 鍚姩 DeviceBroker 澶辫触: {ex.Message}");
+            }
+        }
+
+        private static string FindDeviceBrokerExe()
+        {
+            string baseDir = AppDomain.CurrentDomain.BaseDirectory;
+            var candidates = new[]
+            {
+                Path.Combine(baseDir, "DeviceBroker.exe"),
+                Path.Combine(baseDir, @"DeviceBroker\DeviceBroker.exe"),
+                Path.GetFullPath(Path.Combine(baseDir, @"..\..\..\..\DeviceBroker\bin\Debug\net472\DeviceBroker.exe")),
+                Path.GetFullPath(Path.Combine(baseDir, @"..\..\..\DeviceBroker\bin\Debug\net472\DeviceBroker.exe")),
+            };
+            foreach (var c in candidates)
+            {
+                try { if (File.Exists(c)) return c; } catch { }
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// 鍚� DeviceManager 娉ㄥ唽 CAN 椹卞姩锛堜娇鐢� DriverFactory锛�
+        /// </summary>
+        private void RegisterCanDriver(string deviceId, string endpoint)
+        {
+            try
+            {
+                InfrastructureBootstrapper.RegisterDevice(deviceId, "CAN", endpoint);
+            }
+            catch (Exception ex)
+            {
+                Log($"[WARN] 娉ㄥ唽璁惧 {deviceId} 椹卞姩澶辫触: {ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// 杩愯鑷姩鍖栨楠わ紙灏佽瀹屾暣娴佺▼锛氬紑鎬荤嚎 鈫� 绉熺害 鈫� 鍙戦�� 鈫� 鍥炴敹 鈫� 閲婃斁锛�
+        /// </summary>
+        private async void AutoRunBtn_Click(object sender, RoutedEventArgs e)
+        {
+            if (_autoFacade == null || _canBusService == null)
+            {
+                AutoLog("[ERROR] 鏈嶅姟鏈垵濮嬪寲");
+                return;
+            }
+
+            string txDevice = autoTxDeviceCombo.SelectedItem as string ?? "CAN-001";
+            string rxDevice = autoRxDeviceCombo.SelectedItem as string ?? "CAN-002";
+
+            if (!int.TryParse(autoLoopCount.Text, out int loops) || loops <= 0)
+            {
+                AutoLog("[ERROR] 寰幆娆℃暟蹇呴』涓烘鏁存暟");
+                return;
+            }
+            if (!int.TryParse(autoInterval.Text, out int intervalMs) || intervalMs < 0)
+            {
+                intervalMs = 200;
+            }
+
+            // 纭繚鎬荤嚎宸插惎鍔紙浣跨敤 CAN-001 椹卞姩鏃朵細鑷姩鍚姩锛�
+            EnsureBusRunning();
+
+            // 鍚敤涓鎸夐挳
+            autoRunBtn.IsEnabled = false;
+            autoStopBtn.IsEnabled = true;
+            autoProgress.Maximum = loops;
+            autoProgress.Value = 0;
+            _autoCts = new CancellationTokenSource();
+            var ct = _autoCts.Token;
+
+            AutoLog("鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲");
+            AutoLog($"  CAN 鑰佸寲鑷姩鍖栨楠ゅ紑濮�");
+            AutoLog($"  鍙戦�佽澶�: {txDevice}, 鐩爣璁惧: {rxDevice}");
+            AutoLog($"  寰幆娆℃暟: {loops}, 甯ч棿闅�: {intervalMs}ms");
+            AutoLog("鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲");
+
+            int successCount = 0;
+            int failCount = 0;
+            var swTotal = System.Diagnostics.Stopwatch.StartNew();
+
+            // ===== 鏂规 B锛氫綔鐢ㄥ煙绉熺害锛堜竴娆$敵璇凤紝澶氭浣跨敤锛� =====
+            try
+            {
+                using var lease = await _autoFacade.AcquireAsync(txDevice, "CAN_Aging", ct);
+                AutoLog($"[绉熺害] 宸茶幏寰� {txDevice} 浣滅敤鍩熺绾�: Session={lease.SessionId.Substring(0, 8)}, 鏃跺埢 {lease.AcquiredAt:HH:mm:ss.fff}");
+
+                // ===== 鏂规 C锛氫娇鐢ㄧ被鍨嬪寲鏈嶅姟 =====
+                // 鐩爣璁惧鍏堝惎鐢ㄨ嚜鍔ㄥ搷搴旓紙浠ヤ究鍥炵幆楠岃瘉锛�
+                using var rxLease = await _autoFacade.AcquireAsync(rxDevice, "CAN_Aging_Rx", ct);
+                AutoLog($"[绉熺害] 宸茶幏寰� {rxDevice} 浣滅敤鍩熺绾�: Session={rxLease.SessionId.Substring(0, 8)}");
+                await _canBusService.SetAutoResponseAsync(rxLease, true, ct);
+                AutoLog($"[閰嶇疆] 宸蹭负 {rxDevice} 鍚敤鑷姩鍝嶅簲妯″紡");
+
+                for (int i = 0; i < loops; i++)
+                {
+                    if (ct.IsCancellationRequested) break;
+
+                    uint frameId = (uint)(0x100 + i);
+                    byte[] payload = new byte[] { (byte)i, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x5A };
+
+                    // 鍙戦�侊紙璧� txDevice 绉熺害锛�
+                    bool sent = await _canBusService.SendFrameAsync(lease, frameId, payload, ct);
+                    AutoLog($"[T+{i,3:D3}] TX  {frameId:X3} | {BitConverter.ToString(payload).Replace("-", " ")}  {(sent ? "鉁�" : "鉁�")}");
+
+                    // 鎺ユ敹鍥炲搷锛堣蛋 rxDevice 绉熺害锛�
+                    await Task.Delay(intervalMs, ct);
+                    var echo = await _canBusService.ReceiveFrameAsync(rxLease, ct);
+
+                    if (echo != null)
+                    {
+                        AutoLog($"[R+{i,3:D3}] RX  {echo.FrameIdHex} | {echo.DataHex}  鍖归厤={(echo.FrameId == frameId ? "鉁�" : "鉁�")}");
+                        if (echo.FrameId == frameId && echo.Data.SequenceEqual(payload)) successCount++;
+                        else failCount++;
+                    }
+                    else
+                    {
+                        AutoLog($"[R+{i,3:D3}] RX  鏃犲洖鍝�");
+                        failCount++;
+                    }
+
+                    autoProgress.Value = i + 1;
+                    autoProgressText.Text = $"{i + 1}/{loops} 瀹屾垚";
+                }
+
+                // 鏀跺熬锛氬叧闂嚜鍔ㄥ搷搴�
+                await _canBusService.SetAutoResponseAsync(rxLease, false, ct);
+                AutoLog($"[閰嶇疆] 宸蹭负 {rxDevice} 鍏抽棴鑷姩鍝嶅簲妯″紡");
+            }
+            catch (OperationCanceledException)
+            {
+                AutoLog("[涓] 鐢ㄦ埛鎵嬪姩涓娴嬭瘯");
+            }
+            catch (TimeoutException ex)
+            {
+                AutoLog($"[澶辫触] 绉熺害鐢宠瓒呮椂: {ex.Message}");
+                failCount++;
+            }
+            catch (InvalidOperationException ex)
+            {
+                AutoLog($"[澶辫触] 璧勬簮鍐茬獊: {ex.Message}");
+                failCount++;
+            }
+            catch (Exception ex)
+            {
+                AutoLog($"[寮傚父] {ex.GetType().Name}: {ex.Message}");
+                failCount++;
+            }
+            finally
+            {
+                swTotal.Stop();
+                // 浣跨敤 lease.Dispose() 浼氳嚜鍔ㄩ噴鏀剧绾︼紝鏃犻渶鎵嬪姩鍐欎唬鐮�
+                _autoCts?.Dispose();
+                _autoCts = null;
+
+                AutoLog("");
+                AutoLog("鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲");
+                AutoLog($"  姝ラ瀹屾垚 | 鎴愬姛 {successCount} | 澶辫触 {failCount} | 鎬昏�楁椂 {swTotal.ElapsedMilliseconds}ms");
+                AutoLog($"  绉熺害宸茶嚜鍔ㄩ噴鏀撅紙using 浣滅敤鍩熺粨鏉燂級");
+                AutoLog("鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲");
+
+                autoRunBtn.IsEnabled = true;
+                autoStopBtn.IsEnabled = false;
+                autoProgressText.Text = "灏辩华";
+            }
+        }
+
+        /// <summary>
+        /// 鍏ㄨ嚜鍔烽�愯澶囦竴娆℃敹鍙戯細妯℃嫙 TestPlan 鐨� TestStep锛屽 CAN-001~004 閫愪竴
+        /// 閫氳繃绫诲瀷鍖栨湇鍔�/闂ㄩ潰璋冪敤 CAN 椹卞姩锛屽畬鎴愨�滃彂閫佸懡浠も啋鎺ユ敹鍥炴樉鈥濅竴娆″畬鏁村線杩斻��
+        /// 渚濊禆鎬荤嚎搴旂瓟鍣紙BUS_ECHO锛変娇鍗曡澶囪嚜鍙戣嚜鏀讹紝鏃犻渶瀵圭璁惧銆�
+        /// </summary>
+        private async void PerDeviceCanTest_Click(object sender, RoutedEventArgs e)
+        {
+            if (_autoFacade == null || _canBusService == null)
+            {
+                AutoLog("[ERROR] 鏈嶅姟鏈垵濮嬪寲");
+                return;
+            }
+
+            var devices = new[] { "CAN-001", "CAN-002", "CAN-003", "CAN-004" };
+            perDeviceBtn.IsEnabled = false;
+            autoStopBtn.IsEnabled = true;
+            _autoCts = new CancellationTokenSource();
+            var ct = _autoCts.Token;
+            autoProgress.Maximum = devices.Length * 3 + 2;
+            autoProgress.Value = 0;
+
+            int passed = 0, failed = 0;
+            var sw = System.Diagnostics.Stopwatch.StartNew();
+            AutoLog("鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲");
+            AutoLog("  鍏ㄨ嚜鍔烽�愯澶囦竴娆℃敹鍙戯紙妯℃嫙 TestPlan TestStep锛�");
+            AutoLog("鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲");
+
+            // 鍏ㄥ眬鍓嶇疆锛氬惎鐢ㄦ�荤嚎搴旂瓟鍣紙浣挎瘡娆″彂閫侀兘鑳芥敹鍒板洖鏄撅級
+            await RunStepAsync("鍏ㄥ眬", "鍚敤鎬荤嚎搴旂瓟鍣� BUS_ECHO:ON", async () =>
+                (await _autoFacade.QueryAsync(devices[0], "BUS_ECHO:ON", ct)).Success,
+                ok => { if (ok) passed++; else failed++; autoProgress.Value++; });
+
+            for (int idx = 0; idx < devices.Length; idx++)
+            {
+                if (ct.IsCancellationRequested) break;
+                string dev = devices[idx];
+                uint frameId = (uint)(0x300 + idx);
+                byte[] payload = new byte[] { (byte)idx, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 };
+
+                // TestStep 1锛氬彂閫佸懡浠ゅ抚锛圤neShot锛氬唴閮� 棰勭害鈫掓墽琛屸啋閲婃斁锛�
+                await RunStepAsync(dev, $"鍙戦�� 0x{frameId:X3}", async () =>
+                    await _canBusService.SendFrameAsync(dev, frameId, payload, ct),
+                    ok => { if (ok) passed++; else failed++; autoProgress.Value++; });
+
+                // TestStep 2锛氳Е鍙戝涓� drain锛圔roker 璁惧鍥炴樉缁忔鎹庡甫鍥炲鎴风缂撳啿锛�
+                await RunStepAsync(dev, "瑙﹀彂鍥炴樉鎹庡甫(STATUS)", async () =>
+                    (await _autoFacade.QueryAsync(dev, "STATUS", ct)).Success,
+                    ok => { if (ok) passed++; else failed++; autoProgress.Value++; });
+
+                // TestStep 3锛氭帴鏀跺洖鏄惧苟鏍¢獙甯� ID
+                await RunStepAsync(dev, "鎺ユ敹骞舵牎楠屽洖鏄�", async () =>
+                {
+                    for (int t = 0; t < 10; t++)
+                    {
+                        var recv = await _canBusService.ReceiveFrameAsync(dev, ct);
+                        if (recv != null)
+                        {
+                            bool match = recv.FrameId == frameId;
+                            AutoLog($"    [{dev}] RX {recv.FrameIdHex} | {recv.DataHex}  鍖归厤={(match ? "鉁�" : "鉁�")}");
+                            return match;
+                        }
+                        await Task.Delay(80, ct);
+                    }
+                    AutoLog($"    [{dev}] 鏈敹鍒板洖鏄�");
+                    return false;
+                },
+                ok => { if (ok) passed++; else failed++; autoProgress.Value++; });
+            }
+
+            // 鍏ㄥ眬鏀跺熬锛氬叧闂�荤嚎搴旂瓟鍣�
+            await RunStepAsync("鍏ㄥ眬", "鍏抽棴鎬荤嚎搴旂瓟鍣� BUS_ECHO:OFF", async () =>
+                (await _autoFacade.QueryAsync(devices[0], "BUS_ECHO:OFF", ct)).Success,
+                ok => { if (ok) passed++; else failed++; autoProgress.Value++; });
+
+            sw.Stop();
+            AutoLog("鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�");
+            AutoLog($"  瀹屾垚 | 閫氳繃 {passed} | 澶辫触 {failed} | 鑰楁椂 {sw.ElapsedMilliseconds}ms");
+            AutoLog("鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲");
+            autoProgressText.Text = $"閫氳繃 {passed} / 澶辫触 {failed}";
+            perDeviceBtn.IsEnabled = true;
+            autoStopBtn.IsEnabled = false;
+            _autoCts?.Dispose();
+            _autoCts = null;
+        }
+
+        /// <summary>妯℃嫙 TestPlan 鐨� TestStep锛氬懡鍚嶆楠� + 鏂█ + 璁板綍閫氳繃/澶辫触銆�</summary>
+        private async Task RunStepAsync(string device, string name, Func<Task<bool>> body, Action<bool> onDone)
+        {
+            AutoLog($"鈻� [TestStep] {device} :: {name}");
+            bool ok;
+            try { ok = await body(); }
+            catch (OperationCanceledException) { AutoLog("    [涓] 鐢ㄦ埛鍙栨秷"); throw; }
+            catch (Exception ex) { ok = false; AutoLog($"    寮傚父: {ex.Message}"); }
+            AutoLog($"    {(ok ? "PASS 鉁�" : "FAIL 鉁�")}");
+            onDone(ok);
+        }
+
+        /// <summary>
+        /// 涓褰撳墠鑷姩鍖栨祴璇�
+        /// </summary>
+        private void AutoStopBtn_Click(object sender, RoutedEventArgs e)
+        {
+            _autoCts?.Cancel();
+            AutoLog("[涓] 宸插彂閫佸彇娑堣姹�...");
+        }
+
+        /// <summary>
+        /// 娓呯┖鑷姩鍖栨棩蹇�
+        /// </summary>
+        private void AutoClearLog_Click(object sender, RoutedEventArgs e)
+        {
+            autoLog.Clear();
+        }
+
+        /// <summary>
+        /// 鎬荤嚎鐢卞閮� CanSimulatorServer 鎵樼锛屾湰鍦版棤闇�鍐嶅惎鍔ㄨ繘绋嬪唴鎬荤嚎锛堜繚鐣欑┖瀹炵幇鍏煎鏃ц皟鐢級銆�
+        /// </summary>
+        private void EnsureBusRunning() { }
+
+        /// <summary>
+        /// 鍚戣嚜鍔ㄥ寲鏃ュ織鍖鸿拷鍔犳棩蹇�
+        /// </summary>
+        private void AutoLog(string msg)
+        {
+            string line = $"{DateTime.Now:HH:mm:ss.fff} {msg}";
+            Dispatcher.Invoke(() =>
+            {
+                autoLog.AppendText(line + Environment.NewLine);
+                autoLog.ScrollToEnd();
+            });
+        }
+
+        #endregion
+
         #region CAN 妯℃嫙鍣�
 
         /// <summary>
@@ -663,25 +1096,17 @@
                     return;
                 }
 
-                // 閫氳繃 DriverFactory 鍒涘缓 CAN 椹卞姩鏉ュ惎鍔ㄦ�荤嚎
+                // 鎬荤嚎鐢卞閮ㄦā鎷熷櫒鏈嶅姟鎵樼锛氭澶勪粎楠岃瘉涓�涓妭鐐瑰彲鎺ュ叆锛堣繛閫氭�ф帰娴嬶級
                 var driver = DriverFactory.CreateDriver("CAN_SIM_0", "CAN", $"{busName}:0");
                 driver.ConnectAsync(CancellationToken.None).GetAwaiter().GetResult();
 
-                // 鑾峰彇鎬荤嚎瀹炰緥
-                var buses = CanDriverAdapter.GetAllBuses();
-                if (buses.TryGetValue(busName, out var bus))
-                {
-                    _canBus = bus;
-                    bus.Start();
+                // 鏇存柊 UI
+                canBusIndicator.Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4CAF50"));
+                canBusStatusText.Text = $"宸叉帴鍏ュ閮ㄦā鎷熷櫒 ({busName})";
+                canStartBtn.IsEnabled = false;
+                canStopBtn.IsEnabled = true;
 
-                    // 鏇存柊 UI
-                    canBusIndicator.Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4CAF50"));
-                    canBusStatusText.Text = $"杩愯涓� ({busName})";
-                    canStartBtn.IsEnabled = false;
-                    canStopBtn.IsEnabled = true;
-
-                    Log($"[CAN] 鎬荤嚎 {busName} 宸插惎鍔�");
-                }
+                Log($"[CAN] 宸茶繛鎺ュ閮ㄦā鎷熷櫒鎬荤嚎 {busName}");
             }
             catch (Exception ex)
             {
@@ -720,7 +1145,6 @@
         {
             try
             {
-                CanDriverAdapter.ResetAllBuses();
                 _canBus = null;
 
                 // 鏇存柊 UI
@@ -729,7 +1153,7 @@
                 canStartBtn.IsEnabled = true;
                 canStopBtn.IsEnabled = false;
 
-                Log("[CAN] 鎵�鏈夋�荤嚎宸查噸缃�");
+                Log("[CAN] 鏈湴鐘舵�佸凡閲嶇疆锛堝閮ㄦā鎷熷櫒鎬荤嚎涓嶅彈褰卞搷锛�");
             }
             catch (Exception ex)
             {

--
Gitblit v1.9.1