feat: 完成逻辑分析仪前后端交互
This commit is contained in:
parent
4562be2d01
commit
0410d14d3a
|
@ -1,7 +1,9 @@
|
||||||
import { createInjectionState } from "@vueuse/core";
|
import { createInjectionState } from "@vueuse/core";
|
||||||
import { shallowRef, reactive, ref, computed } from "vue";
|
import { shallowRef, reactive, ref, computed } from "vue";
|
||||||
|
import { Mutex } from "async-mutex";
|
||||||
import {
|
import {
|
||||||
CaptureConfig,
|
CaptureConfig,
|
||||||
|
CaptureStatus,
|
||||||
LogicAnalyzerClient,
|
LogicAnalyzerClient,
|
||||||
GlobalCaptureMode,
|
GlobalCaptureMode,
|
||||||
SignalOperator,
|
SignalOperator,
|
||||||
|
@ -10,6 +12,7 @@ import {
|
||||||
} from "@/APIClient";
|
} from "@/APIClient";
|
||||||
import { AuthManager } from "@/utils/AuthManager";
|
import { AuthManager } from "@/utils/AuthManager";
|
||||||
import { useAlertStore } from "@/components/Alert";
|
import { useAlertStore } from "@/components/Alert";
|
||||||
|
import { useRequiredInjection } from "@/utils/Common";
|
||||||
|
|
||||||
export type LogicDataType = {
|
export type LogicDataType = {
|
||||||
x: number[];
|
x: number[];
|
||||||
|
@ -77,11 +80,15 @@ const defaultColors = [
|
||||||
const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
|
const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
|
||||||
() => {
|
() => {
|
||||||
const logicData = shallowRef<LogicDataType>();
|
const logicData = shallowRef<LogicDataType>();
|
||||||
const alert = useAlertStore();
|
const alert = useRequiredInjection(useAlertStore);
|
||||||
|
|
||||||
|
// 添加互斥锁
|
||||||
|
const operationMutex = new Mutex();
|
||||||
|
|
||||||
// 触发设置相关状态
|
// 触发设置相关状态
|
||||||
const currentGlobalMode = ref<GlobalCaptureMode>(GlobalCaptureMode.AND);
|
const currentGlobalMode = ref<GlobalCaptureMode>(GlobalCaptureMode.AND);
|
||||||
const isApplying = ref(false);
|
const isApplying = ref(false);
|
||||||
|
const isCapturing = ref(false); // 添加捕获状态标识
|
||||||
|
|
||||||
// 通道配置
|
// 通道配置
|
||||||
const channels = reactive<Channel[]>(
|
const channels = reactive<Channel[]>(
|
||||||
|
@ -107,13 +114,13 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
|
||||||
);
|
);
|
||||||
|
|
||||||
// 添加计算属性:获取通道名称数组
|
// 添加计算属性:获取通道名称数组
|
||||||
const channelNames = computed(() =>
|
const channelNames = computed(() =>
|
||||||
channels.map(channel => channel.label)
|
channels.map((channel) => channel.label),
|
||||||
);
|
);
|
||||||
|
|
||||||
// 添加计算属性:获取启用通道的名称数组
|
// 添加计算属性:获取启用通道的名称数组
|
||||||
const enabledChannelNames = computed(() =>
|
const enabledChannels = computed(() =>
|
||||||
channels.filter(channel => channel.enabled).map(channel => channel.label)
|
channels.filter((channel) => channel.enabled),
|
||||||
);
|
);
|
||||||
|
|
||||||
const enableAllChannels = () => {
|
const enableAllChannels = () => {
|
||||||
|
@ -129,6 +136,13 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
|
||||||
};
|
};
|
||||||
|
|
||||||
const setGlobalMode = async (mode: GlobalCaptureMode) => {
|
const setGlobalMode = async (mode: GlobalCaptureMode) => {
|
||||||
|
// 检查是否有其他操作正在进行
|
||||||
|
if (operationMutex.isLocked()) {
|
||||||
|
alert.warn("有其他操作正在进行中,请稍后再试", 3000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const release = await operationMutex.acquire();
|
||||||
try {
|
try {
|
||||||
const client = AuthManager.createAuthenticatedLogicAnalyzerClient();
|
const client = AuthManager.createAuthenticatedLogicAnalyzerClient();
|
||||||
const success = await client.setGlobalTrigMode(mode);
|
const success = await client.setGlobalTrigMode(mode);
|
||||||
|
@ -145,10 +159,19 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("设置全局触发模式失败:", error);
|
console.error("设置全局触发模式失败:", error);
|
||||||
alert?.error("设置全局触发模式失败", 3000);
|
alert?.error("设置全局触发模式失败", 3000);
|
||||||
|
} finally {
|
||||||
|
release();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const applyConfiguration = async () => {
|
const applyConfiguration = async () => {
|
||||||
|
// 检查是否有其他操作正在进行
|
||||||
|
if (operationMutex.isLocked()) {
|
||||||
|
alert.warn("有其他操作正在进行中,请稍后再试", 3000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const release = await operationMutex.acquire();
|
||||||
isApplying.value = true;
|
isApplying.value = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -190,6 +213,7 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
|
||||||
alert?.error("应用配置失败,请检查设备连接", 3000);
|
alert?.error("应用配置失败,请检查设备连接", 3000);
|
||||||
} finally {
|
} finally {
|
||||||
isApplying.value = false;
|
isApplying.value = false;
|
||||||
|
release();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -215,13 +239,123 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
|
||||||
logicData.value = data;
|
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<boolean> => {
|
||||||
|
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 generateTestData = () => {
|
||||||
const sampleRate = 10000; // 10kHz sampling
|
const sampleRate = 10000; // 10kHz sampling
|
||||||
const duration = 1;
|
const duration = 1;
|
||||||
const points = Math.floor(sampleRate * duration);
|
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
|
// Generate 8 channels with different digital patterns
|
||||||
const y = [
|
const y = [
|
||||||
|
@ -246,37 +380,31 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
|
||||||
(_, i) => Math.floor((12.5 * i) / sampleRate) % 2,
|
(_, i) => Math.floor((12.5 * i) / sampleRate) % 2,
|
||||||
),
|
),
|
||||||
// Channel 4: Data signal (pseudo-random pattern)
|
// Channel 4: Data signal (pseudo-random pattern)
|
||||||
Array.from(
|
Array.from({ length: points }, (_, i) =>
|
||||||
{ length: points },
|
Math.abs(Math.floor(Math.sin(i * 0.01) * 10) % 2),
|
||||||
(_, i) => Math.abs( Math.floor(Math.sin(i * 0.01) * 10) % 2 ),
|
|
||||||
),
|
),
|
||||||
// Channel 5: Enable signal (periodic pulse)
|
// Channel 5: Enable signal (periodic pulse)
|
||||||
Array.from(
|
Array.from({ length: points }, (_, i) =>
|
||||||
{ length: points },
|
Math.floor(i / 50) % 10 < 3 ? 1 : 0,
|
||||||
(_, i) => (Math.floor(i / 50) % 10) < 3 ? 1 : 0,
|
|
||||||
),
|
),
|
||||||
// Channel 6: Reset signal (occasional pulse)
|
// Channel 6: Reset signal (occasional pulse)
|
||||||
Array.from(
|
Array.from({ length: points }, (_, i) =>
|
||||||
{ length: points },
|
Math.floor(i / 200) % 20 === 0 ? 1 : 0,
|
||||||
(_, i) => (Math.floor(i / 200) % 20) === 0 ? 1 : 0,
|
|
||||||
),
|
),
|
||||||
// Channel 7: Status signal (slow changing)
|
// Channel 7: Status signal (slow changing)
|
||||||
Array.from(
|
Array.from({ length: points }, (_, i) => Math.floor(i / 1000) % 2),
|
||||||
{ length: points },
|
|
||||||
(_, i) => Math.floor(i / 1000) % 2,
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// 同时更新通道标签为更有意义的名称
|
// 同时更新通道标签为更有意义的名称
|
||||||
const testChannelNames = [
|
const testChannelNames = [
|
||||||
"CLK",
|
"CLK",
|
||||||
"CLK/2",
|
"CLK/2",
|
||||||
"CLK/4",
|
"CLK/4",
|
||||||
"CLK/8",
|
"CLK/8",
|
||||||
"PWM",
|
"PWM",
|
||||||
"ENABLE",
|
"ENABLE",
|
||||||
"RESET",
|
"RESET",
|
||||||
"STATUS"
|
"STATUS",
|
||||||
];
|
];
|
||||||
|
|
||||||
channels.forEach((channel, index) => {
|
channels.forEach((channel, index) => {
|
||||||
|
@ -284,8 +412,9 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
|
||||||
});
|
});
|
||||||
|
|
||||||
// 设置逻辑数据
|
// 设置逻辑数据
|
||||||
|
enableAllChannels();
|
||||||
setLogicData({ x, y, xUnit: "ms" });
|
setLogicData({ x, y, xUnit: "ms" });
|
||||||
|
|
||||||
alert?.success("测试数据生成成功", 2000);
|
alert?.success("测试数据生成成功", 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -296,11 +425,13 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
|
||||||
// 触发设置状态
|
// 触发设置状态
|
||||||
currentGlobalMode,
|
currentGlobalMode,
|
||||||
isApplying,
|
isApplying,
|
||||||
|
isCapturing, // 导出捕获状态
|
||||||
|
isOperationInProgress, // 导出操作进行状态
|
||||||
channels,
|
channels,
|
||||||
signalConfigs,
|
signalConfigs,
|
||||||
enabledChannelCount,
|
enabledChannelCount,
|
||||||
channelNames,
|
channelNames,
|
||||||
enabledChannelNames,
|
enabledChannels,
|
||||||
|
|
||||||
// 选项数据
|
// 选项数据
|
||||||
globalModes,
|
globalModes,
|
||||||
|
@ -314,6 +445,7 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
|
||||||
applyConfiguration,
|
applyConfiguration,
|
||||||
resetConfiguration,
|
resetConfiguration,
|
||||||
setLogicData,
|
setLogicData,
|
||||||
|
startCapture,
|
||||||
generateTestData,
|
generateTestData,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
@ -106,9 +106,20 @@ const updateOptions = shallowRef({
|
||||||
const option = computed((): EChartsOption => {
|
const option = computed((): EChartsOption => {
|
||||||
if (isUndefined(analyzer.logicData.value)) return {};
|
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; // 每个通道之间的间距
|
const channelSpacing = 2; // 每个通道之间的间距
|
||||||
|
|
||||||
|
// 如果没有启用的通道,返回空配置
|
||||||
|
if (channelCount === 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
// 使用单个网格
|
// 使用单个网格
|
||||||
const grids: GridComponentOption[] = [
|
const grids: GridComponentOption[] = [
|
||||||
{
|
{
|
||||||
|
@ -134,7 +145,7 @@ const option = computed((): EChartsOption => {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// 单个Y轴,范围根据通道数量调整
|
// 单个Y轴,范围根据启用通道数量调整
|
||||||
const yAxis: YAXisOption[] = [
|
const yAxis: YAXisOption[] = [
|
||||||
{
|
{
|
||||||
type: "value",
|
type: "value",
|
||||||
|
@ -145,7 +156,7 @@ const option = computed((): EChartsOption => {
|
||||||
formatter: (value: number) => {
|
formatter: (value: number) => {
|
||||||
const channelIndex = Math.round(value / channelSpacing);
|
const channelIndex = Math.round(value / channelSpacing);
|
||||||
return channelIndex < channelCount
|
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(
|
const series: LineSeriesOption[] = enabledChannelIndices.map(
|
||||||
(channelData: number[], index: number) => ({
|
(originalIndex: number, displayIndex: number) => ({
|
||||||
name: analyzer.channelNames.value[index],
|
name: enabledChannels[displayIndex].label,
|
||||||
type: "line",
|
type: "line",
|
||||||
data: channelData.map(
|
data: analyzer.logicData.value!.y[originalIndex].map(
|
||||||
(value: number) => value + index * channelSpacing + 0.2,
|
(value: number) => value + displayIndex * channelSpacing + 0.2,
|
||||||
),
|
),
|
||||||
step: "end",
|
step: "end",
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
width: 2,
|
width: 2,
|
||||||
|
color: enabledChannels[displayIndex].color,
|
||||||
},
|
},
|
||||||
areaStyle: {
|
areaStyle: {
|
||||||
opacity: 0.3,
|
opacity: 0.3,
|
||||||
origin: index * channelSpacing,
|
origin: displayIndex * channelSpacing,
|
||||||
|
color: enabledChannels[displayIndex].color,
|
||||||
},
|
},
|
||||||
symbol: "none",
|
symbol: "none",
|
||||||
// 优化性能配置
|
// 优化性能配置
|
||||||
sampling: "lttb",
|
sampling: "lttb",
|
||||||
// large: true,
|
|
||||||
// largeThreshold: 2000,
|
|
||||||
// progressive: 2000,
|
|
||||||
// 减少动画以避免闪烁
|
// 减少动画以避免闪烁
|
||||||
animation: false,
|
animation: false,
|
||||||
}),
|
}),
|
||||||
|
@ -200,16 +210,15 @@ const option = computed((): EChartsOption => {
|
||||||
|
|
||||||
let tooltip = `Time: ${timeValue.toFixed(3)}${analyzer.logicData.value!.xUnit}<br/>`;
|
let tooltip = `Time: ${timeValue.toFixed(3)}${analyzer.logicData.value!.xUnit}<br/>`;
|
||||||
|
|
||||||
// 显示所有通道在当前时间点的原始数值(0或1)
|
// 只显示启用通道在当前时间点的原始数值(0或1)
|
||||||
if (analyzer.logicData.value) {
|
enabledChannelIndices.forEach(
|
||||||
analyzer.channelNames.value.forEach(
|
(originalIndex: number, displayIndex: number) => {
|
||||||
(channelName: string, index: number) => {
|
const channelName = enabledChannels[displayIndex].label;
|
||||||
const originalValue =
|
const originalValue =
|
||||||
analyzer.logicData.value!.y[index][dataIndex];
|
analyzer.logicData.value!.y[originalIndex][dataIndex];
|
||||||
tooltip += `${channelName}: ${originalValue}<br/>`;
|
tooltip += `${channelName}: ${originalValue}<br/>`;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return tooltip;
|
return tooltip;
|
||||||
}
|
}
|
||||||
|
@ -243,5 +252,4 @@ const option = computed((): EChartsOption => {
|
||||||
series: series,
|
series: series,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in New Issue