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