| | |
| | | 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; |
| | |
| | | private CanBusSimulator? _canBus; |
| | | private CanDebugPanel? _canDebugPanel; |
| | | |
| | | // V3.0:DeviceBroker 宿主子进程(存在 Broker 设备时自动拉起) |
| | | private System.Diagnostics.Process? _brokerProcess; |
| | | |
| | | // 自动化测试服务 |
| | | private IAutoDeviceFacade? _autoFacade; |
| | | private ICanBusService? _canBusService; |
| | | private CancellationTokenSource? _autoCts; |
| | | |
| | | public MainWindow() |
| | | { |
| | | InitializeComponent(); |
| | |
| | | |
| | | private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) |
| | | { |
| | | // 清理 CAN 模拟器 |
| | | _canBus?.Dispose(); |
| | | CanDriverAdapter.ResetAllBuses(); |
| | | // 取消正在运行的自动化测试 |
| | | try { _autoCts?.Cancel(); } catch { } |
| | | |
| | | // 停止 DeviceBroker 宿主(其持有的物理会话随进程退出释放,租约靠 TTL 过期+人工回收) |
| | | try |
| | | { |
| | | if (_brokerProcess != null && !_brokerProcess.HasExited) |
| | | { |
| | | _brokerProcess.Kill(); |
| | | _brokerProcess.WaitForExit(3000); |
| | | } |
| | | _brokerProcess?.Dispose(); |
| | | } |
| | | catch { } |
| | | |
| | | // CAN 模拟器已外部化为独立服务进程,本进程无进程内总线需清理 |
| | | |
| | | // 不删除测试目录,保留数据以便重启后恢复 |
| | | // (如需手动重置,使用工具栏中的 "重置数据" 按钮) |
| | |
| | | |
| | | try |
| | | { |
| | | // 初始化仪器仪表注册插件 |
| | | // 初始化仪器仪表注册插件(建库 data.db) |
| | | _deviceConfigPlugin = new Plugin.DeviceConfig.DeviceConfig(); |
| | | _deviceConfigPlugin.SetBaseFolder(_testDir); |
| | | _deviceConfigPlugin.SetLogger(_testLog); |
| | | _deviceConfigPlugin.InitFinish(); |
| | | deviceConfigHost.Child = _deviceConfigPlugin; |
| | | |
| | | // 初始化硬件调试插件 |
| | | // 预置 CAN 测试设备(必须在基础设施初始化前写入,Bootstrapper 据此注册代理驱动) |
| | | 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(); |
| | |
| | | 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) |
| | | { |
| | |
| | | } |
| | | } |
| | | |
| | | #region 全自动测试(方案 B+C) |
| | | |
| | | /// <summary> |
| | | /// 初始化自动化服务: |
| | | /// 1) 启动 Infrastructure Bootstrapper(ResourceManager/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] 自动化服务未正确初始化,全自动测试 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,Local 设备不受影响。 |
| | | /// </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,Broker 设备链路不可用(请构建 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}),Broker 设备: " + |
| | | 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:发送命令帧(OneShot:内部 预约→执行→释放) |
| | | 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(Broker 设备回显经此捎带回客户端缓冲) |
| | | 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> |
| | |
| | | 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) |
| | | { |
| | |
| | | { |
| | | try |
| | | { |
| | | CanDriverAdapter.ResetAllBuses(); |
| | | _canBus = null; |
| | | |
| | | // 更新 UI |
| | |
| | | canStartBtn.IsEnabled = true; |
| | | canStopBtn.IsEnabled = false; |
| | | |
| | | Log("[CAN] 所有总线已重置"); |
| | | Log("[CAN] 本地状态已重置(外部模拟器总线不受影响)"); |
| | | } |
| | | catch (Exception ex) |
| | | { |