FPGA_WebLab/src/components/Oscilloscope/OscilloscopeManager.ts

379 lines
9.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
autoResetRef,
createInjectionState,
watchDebounced,
} from "@vueuse/core";
import {
shallowRef,
reactive,
ref,
computed,
onMounted,
onUnmounted,
watchEffect,
} from "vue";
import { Mutex } from "async-mutex";
import { OscilloscopeApiClient } from "@/APIClient";
import { AuthManager } from "@/utils/AuthManager";
import { useAlertStore } from "@/components/Alert";
import { useRequiredInjection } from "@/utils/Common";
import type { HubConnection } from "@microsoft/signalr";
import type {
IOscilloscopeHub,
IOscilloscopeReceiver,
} from "@/utils/signalR/TypedSignalR.Client/server.Hubs";
import {
getHubProxyFactory,
getReceiverRegister,
} from "@/utils/signalR/TypedSignalR.Client";
import type {
OscilloscopeDataResponse,
OscilloscopeFullConfig,
} from "@/utils/signalR/server.Hubs";
export type OscilloscopeDataType = {
x: number[];
y: number[] | number[][];
xUnit: "s" | "ms" | "us" | "ns";
yUnit: "V" | "mV" | "uV";
adFrequency: number;
adVpp: number;
adMax: number;
adMin: number;
};
// 默认配置
const DEFAULT_CONFIG: OscilloscopeFullConfig = {
captureEnabled: false,
triggerLevel: 128,
triggerRisingEdge: true,
horizontalShift: 0,
decimationRate: 50,
captureFrequency: 100,
};
// 采样频率常量(后端返回)
const [useProvideOscilloscope, useOscilloscopeState] = createInjectionState(
() => {
// Global Store
const alert = useRequiredInjection(useAlertStore);
// Data
const oscData = shallowRef<OscilloscopeDataType>();
const clearOscilloscopeData = () => {
oscData.value = undefined;
};
// SignalR Hub
const oscilloscopeHub = shallowRef<{
connection: HubConnection;
proxy: IOscilloscopeHub;
} | null>(null);
const oscilloscopeReceiver: IOscilloscopeReceiver = {
onDataReceived: async (data) => {
analyzeOscilloscopeData(data);
},
};
onMounted(() => {
initHub();
});
onUnmounted(() => {
clearHub();
});
function initHub() {
if (oscilloscopeHub.value) return;
const connection = AuthManager.createHubConnection("OscilloscopeHub");
const proxy =
getHubProxyFactory("IOscilloscopeHub").createHubProxy(connection);
getReceiverRegister("IOscilloscopeReceiver").register(
connection,
oscilloscopeReceiver,
);
connection.start();
oscilloscopeHub.value = { connection, proxy };
}
function clearHub() {
if (!oscilloscopeHub.value) return;
oscilloscopeHub.value.connection.stop();
oscilloscopeHub.value = null;
}
function reinitializeHub() {
clearHub();
initHub();
}
function getHubProxy() {
if (!oscilloscopeHub.value) throw new Error("Hub not initialized");
return oscilloscopeHub.value.proxy;
}
// 互斥锁
const operationMutex = new Mutex();
// 状态
const isApplying = ref(false);
const isCapturing = ref(false);
const isAutoApplying = ref(false);
// 配置
const config = reactive<OscilloscopeFullConfig>({ ...DEFAULT_CONFIG });
watchDebounced(
config,
() => {
if (!isAutoApplying.value) return;
if (
!isApplying.value ||
!isCapturing.value ||
!operationMutex.isLocked()
) {
applyConfiguration();
}
},
{ debounce: 200, maxWait: 1000 },
);
// 应用配置
const applyConfiguration = async () => {
if (operationMutex.isLocked()) {
alert.warn("有其他操作正在进行中,请稍后再试", 3000);
return;
}
const release = await operationMutex.acquire();
isApplying.value = true;
try {
const proxy = getHubProxy();
const success = await proxy.initialize(config);
if (success) {
alert.success("示波器配置已应用", 2000);
} else {
throw new Error("应用失败");
}
} catch (error) {
if (error instanceof Error && error.message === "Hub not initialized")
reinitializeHub();
alert.error("应用配置失败", 3000);
} finally {
isApplying.value = false;
release();
}
};
// 重置配置
const resetConfiguration = () => {
Object.assign(config, { ...DEFAULT_CONFIG });
alert.info("配置已重置", 2000);
};
// 采样点数(由后端数据决定)
const sampleCount = ref(0);
// 采样周期ns由adFrequency计算
const samplePeriodNs = computed(() =>
oscData.value?.adFrequency
? 1_000_000_000 / oscData.value.adFrequency
: 200,
);
const analyzeOscilloscopeData = (resp: OscilloscopeDataResponse) => {
// 解析波形数据
const binaryString = atob(resp.waveformData);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
sampleCount.value = bytes.length;
// 构建时间轴
const x = Array.from(
{ length: bytes.length },
(_, i) => (i * samplePeriodNs.value) / 1000, // us
);
const y = Array.from(bytes);
oscData.value = {
x,
y,
xUnit: "us",
yUnit: "V",
adFrequency: resp.aDFrequency,
adVpp: resp.aDVpp,
adMax: resp.aDMax,
adMin: resp.aDMin,
};
};
// 获取数据
const getOscilloscopeData = async () => {
try {
const proxy = getHubProxy();
const resp = await proxy.getData();
analyzeOscilloscopeData(resp);
} catch (error) {
alert.error("获取示波器数据失败", 3000);
}
};
// 启动捕获
const startCapture = async () => {
if (operationMutex.isLocked()) {
alert.warn("有其他操作正在进行中,请稍后再试", 3000);
return;
}
isCapturing.value = true;
const release = await operationMutex.acquire();
try {
const proxy = getHubProxy();
const started = await proxy.startCapture();
if (!started) throw new Error("无法启动捕获");
alert.info("开始捕获...", 2000);
} catch (error) {
alert.error("捕获失败", 3000);
isCapturing.value = false;
} finally {
release();
}
};
// 停止捕获
const stopCapture = async () => {
if (!isCapturing.value) {
alert.warn("当前没有正在进行的捕获操作", 2000);
return;
}
isCapturing.value = false;
const release = await operationMutex.acquire();
try {
const proxy = getHubProxy();
const stopped = await proxy.stopCapture();
if (!stopped) throw new Error("无法停止捕获");
alert.info("捕获已停止", 2000);
} catch (error) {
alert.error("停止捕获失败", 3000);
} finally {
release();
}
};
const toggleCapture = async () => {
if (isCapturing.value) {
await stopCapture();
} else {
await startCapture();
}
};
// 更新触发参数
const updateTrigger = async (level: number, risingEdge: boolean) => {
const client = AuthManager.createClient(OscilloscopeApiClient);
try {
const ok = await client.updateTrigger(level, risingEdge);
if (ok) {
config.triggerLevel = level;
config.triggerRisingEdge = risingEdge;
alert.success("触发参数已更新", 2000);
} else {
throw new Error();
}
} catch {
alert.error("更新触发参数失败", 2000);
}
};
// 更新采样参数
const updateSampling = async (
horizontalShift: number,
decimationRate: number,
) => {
const client = AuthManager.createClient(OscilloscopeApiClient);
try {
const ok = await client.updateSampling(horizontalShift, decimationRate);
if (ok) {
config.horizontalShift = horizontalShift;
config.decimationRate = decimationRate;
alert.success("采样参数已更新", 2000);
} else {
throw new Error();
}
} catch {
alert.error("更新采样参数失败", 2000);
}
};
// 手动刷新RAM
const refreshRAM = async () => {
const client = AuthManager.createClient(OscilloscopeApiClient);
try {
const ok = await client.refreshRAM();
if (ok) {
// alert.success("RAM已刷新", 2000);
} else {
throw new Error();
}
} catch {
alert.error("刷新RAM失败", 2000);
}
};
// 生成测试数据
const generateTestData = () => {
const freq = 5_000_000;
const duration = 0.001; // 1ms
const points = Math.floor(freq * duration);
const x = Array.from(
{ length: points },
(_, i) => (i * 1_000_000_000) / freq / 1000,
);
const y = Array.from({ length: points }, (_, i) =>
Math.floor(Math.sin(i * 0.01) * 127 + 128),
);
oscData.value = {
x,
y,
xUnit: "us",
yUnit: "V",
adFrequency: freq,
adVpp: 2.0,
adMax: 255,
adMin: 0,
};
alert.success("测试数据生成成功", 2000);
};
return {
oscData,
config,
isApplying,
isCapturing,
isAutoApplying,
sampleCount,
samplePeriodNs,
applyConfiguration,
resetConfiguration,
clearOscilloscopeData,
getOscilloscopeData,
startCapture,
stopCapture,
toggleCapture,
updateTrigger,
updateSampling,
refreshRAM,
generateTestData,
};
},
);
export { useProvideOscilloscope, useOscilloscopeState, DEFAULT_CONFIG };