379 lines
9.7 KiB
TypeScript
379 lines
9.7 KiB
TypeScript
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 };
|