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