diff --git a/server/src/Peripherals/CameraClient.cs b/server/src/Peripherals/CameraClient.cs index 79d74db..0b053ee 100644 --- a/server/src/Peripherals/CameraClient.cs +++ b/server/src/Peripherals/CameraClient.cs @@ -213,6 +213,7 @@ class Camera /// 包含图像数据的字节数组 public async ValueTask> ReadFrame() { + // 只在第一次或出错时清除UDP缓冲区,避免每帧都清除造成延迟 MsgBus.UDPServer.ClearUDPData(this.address, this.taskID); logger.Trace($"Reading frame from camera {this.address}"); @@ -252,6 +253,8 @@ class Camera /// 配置结果 public async ValueTask> ConfigureRegisters(UInt16[][] registerTable, int? customDelayMs = null) { + MsgBus.UDPServer.ClearUDPData(this.address, this.taskID); + var i2c = new Peripherals.I2cClient.I2c(this.address, this.port, this.taskID, this.timeout); foreach (var cmd in registerTable) @@ -1291,20 +1294,20 @@ class Camera logger.Error($"读取自动对焦初始化状态失败: {readResult.Error}"); return new(readResult.Error); } - + logger.Trace($"自动对焦初始化状态检查, state=0x{readResult.Value:X2}"); - + if (readResult.Value == 0x70) { break; // 初始化完成 } - + if (iteration == 1) { logger.Error($"自动对焦初始化状态检查超时!! state=0x{readResult.Value:X2}"); return new(new Exception($"自动对焦初始化状态检查超时, state=0x{readResult.Value:X2}")); } - + await Task.Delay(1); } diff --git a/server/src/UdpServer.cs b/server/src/UdpServer.cs index 6e8eeca..a1f23ca 100644 --- a/server/src/UdpServer.cs +++ b/server/src/UdpServer.cs @@ -484,6 +484,151 @@ public class UDPServer // 清空队列的最有效方式是替换为新的队列 udpData.TryUpdate(key, new ConcurrentQueue(), dataQueue); } + + // 强制进行ARP刷新,防止后续传输时造成影响 + FlushArpEntry(ipAddr); + } + + /// + /// 跨平台ARP缓存刷新 + /// + /// 目标IP地址 + private void FlushArpEntry(string ipAddr) + { + try + { + // 验证IP地址格式 + if (!IPAddress.TryParse(ipAddr, out var _)) + { + logger.Warn($"Invalid IP address format: {ipAddr}"); + return; + } + + string command; + string arguments; + + if (OperatingSystem.IsWindows()) + { + // Windows: arp -d + command = "arp"; + arguments = $"-d {ipAddr}"; + } + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + // Linux/macOS: ip neigh del dev (优先使用 ip 命令) + // 如果 ip 命令不可用,则使用 arp -d + if (IsCommandAvailable("ip")) + { + command = "ip"; + arguments = $"neigh flush {ipAddr}"; + } + else if (IsCommandAvailable("arp")) + { + command = "arp"; + arguments = $"-d {ipAddr}"; + } + else + { + logger.Warn("Neither 'ip' nor 'arp' command is available for ARP cache flush"); + return; + } + } + else + { + logger.Warn($"Unsupported operating system for ARP cache flush"); + return; + } + + // 异步执行命令,避免阻塞主线程 + Task.Run(() => ExecuteArpCommand(command, arguments, ipAddr)); + } + catch (Exception ex) + { + logger.Error($"Error during ARP cache flush for {ipAddr}: {ex.Message}"); + } + } + + /// + /// 检查系统命令是否可用 + /// + /// 命令名称 + /// 命令是否可用 + private bool IsCommandAvailable(string command) + { + try + { + var process = new System.Diagnostics.Process + { + StartInfo = new System.Diagnostics.ProcessStartInfo + { + FileName = OperatingSystem.IsWindows() ? "where" : "which", + Arguments = command, + UseShellExecute = false, + RedirectStandardOutput = true, + CreateNoWindow = true + } + }; + + process.Start(); + process.WaitForExit(1000); // 1秒超时 + return process.ExitCode == 0; + } + catch + { + return false; + } + } + + /// + /// 执行ARP命令 + /// + /// 命令 + /// 参数 + /// IP地址 + private void ExecuteArpCommand(string command, string arguments, string ipAddr) + { + try + { + var process = new System.Diagnostics.Process + { + StartInfo = new System.Diagnostics.ProcessStartInfo + { + FileName = command, + Arguments = arguments, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + } + }; + + process.Start(); + + // 设置超时时间,避免进程挂起 + if (process.WaitForExit(5000)) // 5秒超时 + { + var output = process.StandardOutput.ReadToEnd(); + var error = process.StandardError.ReadToEnd(); + + if (process.ExitCode == 0) + { + logger.Debug($"ARP cache flush successful for {ipAddr}"); + } + else + { + logger.Warn($"ARP cache flush failed for {ipAddr}. Exit code: {process.ExitCode}, Error: {error}"); + } + } + else + { + process.Kill(); + logger.Warn($"ARP cache flush command timed out for {ipAddr}"); + } + } + catch (Exception ex) + { + logger.Error($"Failed to execute ARP cache flush command for {ipAddr}: {ex.Message}"); + } } /// diff --git a/src/components/LogicAnalyzer/LogicAnalyzerManager.ts b/src/components/LogicAnalyzer/LogicAnalyzerManager.ts index cdc196a..966169e 100644 --- a/src/components/LogicAnalyzer/LogicAnalyzerManager.ts +++ b/src/components/LogicAnalyzer/LogicAnalyzerManager.ts @@ -1,7 +1,9 @@ import { createInjectionState } from "@vueuse/core"; import { shallowRef, reactive, ref, computed } from "vue"; +import { Mutex } from "async-mutex"; import { CaptureConfig, + CaptureStatus, LogicAnalyzerClient, GlobalCaptureMode, SignalOperator, @@ -10,6 +12,7 @@ import { } from "@/APIClient"; import { AuthManager } from "@/utils/AuthManager"; import { useAlertStore } from "@/components/Alert"; +import { useRequiredInjection } from "@/utils/Common"; export type LogicDataType = { x: number[]; @@ -77,11 +80,15 @@ const defaultColors = [ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( () => { const logicData = shallowRef(); - const alert = useAlertStore(); + const alert = useRequiredInjection(useAlertStore); + + // 添加互斥锁 + const operationMutex = new Mutex(); // 触发设置相关状态 const currentGlobalMode = ref(GlobalCaptureMode.AND); const isApplying = ref(false); + const isCapturing = ref(false); // 添加捕获状态标识 // 通道配置 const channels = reactive( @@ -107,13 +114,13 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( ); // 添加计算属性:获取通道名称数组 - const channelNames = computed(() => - channels.map(channel => channel.label) + const channelNames = computed(() => + channels.map((channel) => channel.label), ); // 添加计算属性:获取启用通道的名称数组 - const enabledChannelNames = computed(() => - channels.filter(channel => channel.enabled).map(channel => channel.label) + const enabledChannels = computed(() => + channels.filter((channel) => channel.enabled), ); const enableAllChannels = () => { @@ -129,6 +136,13 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( }; const setGlobalMode = async (mode: GlobalCaptureMode) => { + // 检查是否有其他操作正在进行 + if (operationMutex.isLocked()) { + alert.warn("有其他操作正在进行中,请稍后再试", 3000); + return; + } + + const release = await operationMutex.acquire(); try { const client = AuthManager.createAuthenticatedLogicAnalyzerClient(); const success = await client.setGlobalTrigMode(mode); @@ -145,10 +159,19 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( } catch (error) { console.error("设置全局触发模式失败:", error); alert?.error("设置全局触发模式失败", 3000); + } finally { + release(); } }; const applyConfiguration = async () => { + // 检查是否有其他操作正在进行 + if (operationMutex.isLocked()) { + alert.warn("有其他操作正在进行中,请稍后再试", 3000); + return; + } + + const release = await operationMutex.acquire(); isApplying.value = true; try { @@ -190,6 +213,7 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( alert?.error("应用配置失败,请检查设备连接", 3000); } finally { isApplying.value = false; + release(); } }; @@ -215,13 +239,123 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( logicData.value = data; }; + const startCapture = async () => { + // 检查是否有其他操作正在进行 + if (operationMutex.isLocked()) { + alert.warn("有其他操作正在进行中,请稍后再试", 3000); + return; + } + + const release = await operationMutex.acquire(); + isCapturing.value = true; + + try { + const client = AuthManager.createAuthenticatedLogicAnalyzerClient(); + + // 1. 设置捕获模式为开始捕获 + const captureStarted = await client.setCaptureMode(true, false); + if (!captureStarted) { + throw new Error("无法启动捕获"); + } + + alert?.info("开始捕获信号...", 2000); + + // 2. 轮询捕获状态 + const pollCaptureStatus = async (): Promise => { + const status = await client.getCaptureStatus(); + + // 检查是否捕获完成 + if (status === CaptureStatus.CaptureDone) { + return true; + } + + // 检查是否仍在捕获中 + if ( + status === CaptureStatus.CaptureBusy || + status === CaptureStatus.CaptureOn || + status === CaptureStatus.CaptureForce + ) { + // 等待500毫秒后继续轮询 + await new Promise((resolve) => setTimeout(resolve, 500)); + return await pollCaptureStatus(); + } + + // 其他状态视为错误 + throw new Error(`捕获状态异常: ${status}`); + }; + + // 等待捕获完成 + await pollCaptureStatus(); + + // 3. 获取捕获数据 + const base64Data = await client.getCaptureData(); + + // 4. 将base64数据转换为bytes + const binaryString = atob(base64Data); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + // 5. 解析数据为8个通道的数字信号 + const sampleCount = bytes.length; + const timeStep = 0.1; // 假设每个采样点间隔0.1ms + + // 创建时间轴 + const x = Array.from({ length: sampleCount }, (_, i) => i * timeStep); + + // 创建8个通道的数据 + const y: number[][] = Array.from( + { length: 8 }, + () => new Array(sampleCount), + ); + + // 解析每个字节的8个位到对应通道 + for (let i = 0; i < sampleCount; i++) { + const byte = bytes[i]; + for (let channel = 0; channel < 8; channel++) { + // bit0对应ch0, bit1对应ch1, ..., bit7对应ch7 + y[channel][i] = (byte >> channel) & 1; + } + } + + // 6. 设置逻辑数据 + const logicData: LogicDataType = { + x, + y, + xUnit: "ms", + }; + + setLogicData(logicData); + + alert?.success(`捕获完成!获得 ${sampleCount} 个采样点`, 3000); + } catch (error) { + console.error("捕获失败:", error); + alert?.error( + `捕获失败: ${error instanceof Error ? error.message : "未知错误"}`, + 3000, + ); + } finally { + isCapturing.value = false; + release(); + } + }; + + // 添加检查操作状态的计算属性 + const isOperationInProgress = computed( + () => isApplying.value || isCapturing.value || operationMutex.isLocked(), + ); + // 添加生成测试数据的方法 const generateTestData = () => { const sampleRate = 10000; // 10kHz sampling const duration = 1; const points = Math.floor(sampleRate * duration); - const x = Array.from({ length: points }, (_, i) => (i / sampleRate) * 1000); // time in ms + const x = Array.from( + { length: points }, + (_, i) => (i / sampleRate) * 1000, + ); // time in ms // Generate 8 channels with different digital patterns const y = [ @@ -246,37 +380,31 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( (_, i) => Math.floor((12.5 * i) / sampleRate) % 2, ), // Channel 4: Data signal (pseudo-random pattern) - Array.from( - { length: points }, - (_, i) => Math.abs( Math.floor(Math.sin(i * 0.01) * 10) % 2 ), + Array.from({ length: points }, (_, i) => + Math.abs(Math.floor(Math.sin(i * 0.01) * 10) % 2), ), // Channel 5: Enable signal (periodic pulse) - Array.from( - { length: points }, - (_, i) => (Math.floor(i / 50) % 10) < 3 ? 1 : 0, + Array.from({ length: points }, (_, i) => + Math.floor(i / 50) % 10 < 3 ? 1 : 0, ), // Channel 6: Reset signal (occasional pulse) - Array.from( - { length: points }, - (_, i) => (Math.floor(i / 200) % 20) === 0 ? 1 : 0, + Array.from({ length: points }, (_, i) => + Math.floor(i / 200) % 20 === 0 ? 1 : 0, ), // Channel 7: Status signal (slow changing) - Array.from( - { length: points }, - (_, i) => Math.floor(i / 1000) % 2, - ), + Array.from({ length: points }, (_, i) => Math.floor(i / 1000) % 2), ]; // 同时更新通道标签为更有意义的名称 const testChannelNames = [ "CLK", - "CLK/2", + "CLK/2", "CLK/4", "CLK/8", "PWM", "ENABLE", "RESET", - "STATUS" + "STATUS", ]; channels.forEach((channel, index) => { @@ -284,8 +412,9 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( }); // 设置逻辑数据 + enableAllChannels(); setLogicData({ x, y, xUnit: "ms" }); - + alert?.success("测试数据生成成功", 2000); }; @@ -296,11 +425,13 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( // 触发设置状态 currentGlobalMode, isApplying, + isCapturing, // 导出捕获状态 + isOperationInProgress, // 导出操作进行状态 channels, signalConfigs, enabledChannelCount, channelNames, - enabledChannelNames, + enabledChannels, // 选项数据 globalModes, @@ -314,6 +445,7 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( applyConfiguration, resetConfiguration, setLogicData, + startCapture, generateTestData, }; }, diff --git a/src/components/LogicAnalyzer/LogicalWaveFormDisplay.vue b/src/components/LogicAnalyzer/LogicalWaveFormDisplay.vue index 8d4073f..92f81c2 100644 --- a/src/components/LogicAnalyzer/LogicalWaveFormDisplay.vue +++ b/src/components/LogicAnalyzer/LogicalWaveFormDisplay.vue @@ -106,9 +106,20 @@ const updateOptions = shallowRef({ const option = computed((): EChartsOption => { if (isUndefined(analyzer.logicData.value)) return {}; - const channelCount = analyzer.logicData.value.y.length; + // 只获取启用的通道 + const enabledChannels = analyzer.enabledChannels.value; + const enabledChannelIndices = analyzer.channels + .map((channel, index) => (channel.enabled ? index : -1)) + .filter((index) => index !== -1); + + const channelCount = enabledChannels.length; const channelSpacing = 2; // 每个通道之间的间距 + // 如果没有启用的通道,返回空配置 + if (channelCount === 0) { + return {}; + } + // 使用单个网格 const grids: GridComponentOption[] = [ { @@ -134,7 +145,7 @@ const option = computed((): EChartsOption => { }, ]; - // 单个Y轴,范围根据通道数量调整 + // 单个Y轴,范围根据启用通道数量调整 const yAxis: YAXisOption[] = [ { type: "value", @@ -145,7 +156,7 @@ const option = computed((): EChartsOption => { formatter: (value: number) => { const channelIndex = Math.round(value / channelSpacing); return channelIndex < channelCount - ? analyzer.channelNames.value[channelIndex] + ? enabledChannels[channelIndex].label : ""; }, }, @@ -153,28 +164,27 @@ const option = computed((): EChartsOption => { }, ]; - // 创建系列数据,每个通道有不同的Y偏移 - const series: LineSeriesOption[] = analyzer.logicData.value.y.map( - (channelData: number[], index: number) => ({ - name: analyzer.channelNames.value[index], + // 创建系列数据,只包含启用的通道 + const series: LineSeriesOption[] = enabledChannelIndices.map( + (originalIndex: number, displayIndex: number) => ({ + name: enabledChannels[displayIndex].label, type: "line", - data: channelData.map( - (value: number) => value + index * channelSpacing + 0.2, + data: analyzer.logicData.value!.y[originalIndex].map( + (value: number) => value + displayIndex * channelSpacing + 0.2, ), step: "end", lineStyle: { width: 2, + color: enabledChannels[displayIndex].color, }, areaStyle: { opacity: 0.3, - origin: index * channelSpacing, + origin: displayIndex * channelSpacing, + color: enabledChannels[displayIndex].color, }, symbol: "none", // 优化性能配置 sampling: "lttb", - // large: true, - // largeThreshold: 2000, - // progressive: 2000, // 减少动画以避免闪烁 animation: false, }), @@ -200,16 +210,15 @@ const option = computed((): EChartsOption => { let tooltip = `Time: ${timeValue.toFixed(3)}${analyzer.logicData.value!.xUnit}
`; - // 显示所有通道在当前时间点的原始数值(0或1) - if (analyzer.logicData.value) { - analyzer.channelNames.value.forEach( - (channelName: string, index: number) => { - const originalValue = - analyzer.logicData.value!.y[index][dataIndex]; - tooltip += `${channelName}: ${originalValue}
`; - }, - ); - } + // 只显示启用通道在当前时间点的原始数值(0或1) + enabledChannelIndices.forEach( + (originalIndex: number, displayIndex: number) => { + const channelName = enabledChannels[displayIndex].label; + const originalValue = + analyzer.logicData.value!.y[originalIndex][dataIndex]; + tooltip += `${channelName}: ${originalValue}
`; + }, + ); return tooltip; } @@ -243,5 +252,4 @@ const option = computed((): EChartsOption => { series: series, }; }); -