This repository has been archived on 2025-10-29. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
FPGA_WebLab/src/stores/equipments.ts

404 lines
11 KiB
TypeScript

import { ref, reactive, watchPostEffect, onMounted, onUnmounted } from "vue";
import { defineStore } from "pinia";
import { useLocalStorage } from "@vueuse/core";
import { isString, toNumber, isUndefined, type Dictionary } from "lodash";
import z from "zod";
import { isNumber } from "mathjs";
import { Mutex, withTimeout } from "async-mutex";
import { useConstraintsStore } from "@/stores/constraints";
import { useDialogStore } from "./dialog";
import {
base64ToArrayBuffer,
toFileParameterOrUndefined,
} from "@/utils/Common";
import { AuthManager } from "@/utils/AuthManager";
import { HubConnection, HubConnectionState } from "@microsoft/signalr";
import {
getHubProxyFactory,
getReceiverRegister,
} from "@/utils/signalR/TypedSignalR.Client";
import {
JtagClient,
MatrixKeyClient,
PowerClient,
ResourceClient,
ResourcePurpose,
type ResourceInfo,
} from "@/APIClient";
import type {
IDigitalTubesHub,
IJtagHub,
} from "@/utils/signalR/TypedSignalR.Client/server.Hubs";
export const useEquipments = defineStore("equipments", () => {
// Global Stores
const constrainsts = useConstraintsStore();
const dialog = useDialogStore();
const boardAddr = useLocalStorage("fpga-board-addr", "127.0.0.1");
const boardPort = useLocalStorage("fpga-board-port", 1234);
// Jtag
const enableJtagBoundaryScan = ref(false);
const jtagBitstream = ref<File>();
const jtagBoundaryScanFreq = ref(100);
const jtagUserBitstreams = ref<ResourceInfo[]>([]);
const jtagClientMutex = withTimeout(
new Mutex(),
1000,
new Error("JtagClient Mutex Timeout!"),
);
const jtagHubConnection = ref<HubConnection>();
const jtagHubProxy = ref<IJtagHub>();
onMounted(async () => {
// 每次挂载都重新创建连接
jtagHubConnection.value = AuthManager.createHubConnection("JtagHub");
jtagHubProxy.value = getHubProxyFactory("IJtagHub").createHubProxy(
jtagHubConnection.value,
);
getReceiverRegister("IJtagReceiver").register(jtagHubConnection.value, {
onReceiveBoundaryScanData: async (msg) => {
constrainsts.batchSetConstraintStates(msg);
},
});
await jtagHubConnection.value.start();
});
onUnmounted(() => {
// 断开连接,清理资源
if (jtagHubConnection.value) {
jtagHubConnection.value.stop();
jtagHubConnection.value = undefined;
jtagHubProxy.value = undefined;
}
});
async function jtagBoundaryScanSetOnOff(enable: boolean) {
if (isUndefined(jtagHubProxy.value)) {
console.error("JtagHub Not Initialize...");
return;
}
if (enable) {
const ret = await jtagHubProxy.value.startBoundaryScan(
jtagBoundaryScanFreq.value,
);
if (!ret) {
console.error("Failed to start boundary scan");
return;
}
} else {
const ret = await jtagHubProxy.value.stopBoundaryScan();
if (!ret) {
console.error("Failed to stop boundary scan");
return;
}
}
enableJtagBoundaryScan.value = enable;
}
async function jtagUploadBitstream(
bitstream: File,
examId?: string,
): Promise<string | null> {
try {
// 自动开启电源
await powerSetOnOff(true);
const resourceClient = AuthManager.createClient(ResourceClient);
const resp = await resourceClient.addResource(
"bitstream",
ResourcePurpose.User,
examId || null,
toFileParameterOrUndefined(bitstream),
);
// 如果上传成功,设置为当前选中的比特流
if (resp && resp.id !== undefined && resp.id !== null) {
return resp.id;
}
return null;
} catch (e) {
dialog.error("上传错误");
console.error(e);
return null;
}
}
async function jtagDownloadBitstream(bitstreamId?: string): Promise<string> {
if (bitstreamId === null || bitstreamId === undefined) {
dialog.error("请先选择要下载的比特流");
return "";
}
const release = await jtagClientMutex.acquire();
try {
// 自动开启电源
await powerSetOnOff(true);
const jtagClient = AuthManager.createClient(JtagClient);
const resp = await jtagClient.downloadBitstream(
boardAddr.value,
boardPort.value,
bitstreamId,
);
return resp;
} catch (e) {
dialog.error("下载错误");
console.error(e);
throw e;
} finally {
release();
}
}
async function jtagGetIDCode(isQuiet: boolean = false): Promise<number> {
const release = await jtagClientMutex.acquire();
try {
// 自动开启电源
await powerSetOnOff(true);
const jtagClient = AuthManager.createClient(JtagClient);
const resp = await jtagClient.getDeviceIDCode(
boardAddr.value,
boardPort.value,
);
return resp;
} catch (e) {
if (!isQuiet) dialog.error("获取IDCode错误");
return 0xffff_ffff;
} finally {
release();
}
}
async function jtagSetSpeed(speed: number): Promise<boolean> {
const release = await jtagClientMutex.acquire();
try {
// 自动开启电源
await powerSetOnOff(true);
const jtagClient = AuthManager.createClient(JtagClient);
const resp = await jtagClient.setSpeed(
boardAddr.value,
boardPort.value,
speed,
);
return resp;
} catch (e) {
dialog.error("设置Jtag速度失败");
return false;
} finally {
release();
}
}
// Matrix Key
const enableMatrixKey = ref(false);
const matrixKeyStates = reactive(new Array<boolean>(16).fill(false));
const matrixKeypadClientMutex = withTimeout(
new Mutex(),
1000,
new Error("Matrixkeyclient Mutex Timeout!"),
);
function setMatrixKey(
keyNum: number | string | undefined,
keyValue: boolean,
): boolean {
let _keyNum: number;
if (isString(keyNum)) {
_keyNum = toNumber(keyNum);
} else if (isNumber(keyNum)) {
_keyNum = keyNum;
} else {
return false;
}
if (z.number().nonnegative().max(16).safeParse(_keyNum).success) {
matrixKeyStates[_keyNum] = keyValue;
return true;
}
return false;
}
async function matrixKeypadSetKeyStates(keyStates: boolean[]) {
const release = await matrixKeypadClientMutex.acquire();
try {
const matrixKeypadClient = AuthManager.createClient(MatrixKeyClient);
const resp = await matrixKeypadClient.setMatrixKeyStatus(
boardAddr.value,
boardPort.value,
keyStates,
);
return resp;
} catch (e) {
dialog.error("设置矩阵键盘时,服务器发生错误");
return false;
} finally {
release();
}
}
async function matrixKeypadEnable(enable: boolean) {
const release = await matrixKeypadClientMutex.acquire();
try {
const matrixKeypadClient = AuthManager.createClient(MatrixKeyClient);
if (enable) {
const resp = await matrixKeypadClient.enabelMatrixKey(
boardAddr.value,
boardPort.value,
);
enableMatrixKey.value = resp;
return resp;
} else {
const resp = await matrixKeypadClient.disableMatrixKey(
boardAddr.value,
boardPort.value,
);
enableMatrixKey.value = !resp;
return resp;
}
} catch (e) {
enableMatrixKey.value = false;
dialog.error("设置矩阵键盘是否启用时,服务器发生错误");
return false;
} finally {
release();
}
}
// Power
const powerClientMutex = withTimeout(
new Mutex(),
1000,
new Error("Matrixkeyclient Mutex Timeout!"),
);
const enablePower = ref(false);
async function powerSetOnOff(enable: boolean) {
const release = await powerClientMutex.acquire();
try {
const powerClient = AuthManager.createClient(PowerClient);
const resp = await powerClient.setPowerOnOff(
boardAddr.value,
boardPort.value,
enable,
);
return resp;
} catch (e) {
dialog.error("无法开关电源");
console.error(e);
return false;
} finally {
release();
}
}
// Seven Segment Display
const enableSevenSegmentDisplay = ref(false);
const sevenSegmentDisplayFrequency = ref(100);
const sevenSegmentDisplayData = ref<Uint8Array>();
const sevenSegmentDisplayHub = ref<HubConnection>();
const sevenSegmentDisplayHubProxy = ref<IDigitalTubesHub>();
async function sevenSegmentDisplaySetOnOff(enable: boolean) {
if (!sevenSegmentDisplayHub.value || !sevenSegmentDisplayHubProxy.value)
return;
if (sevenSegmentDisplayHub.value.state === HubConnectionState.Disconnected)
await sevenSegmentDisplayHub.value.start();
if (enable) {
await sevenSegmentDisplayHubProxy.value.startScan();
} else {
await sevenSegmentDisplayHubProxy.value.stopScan();
}
}
async function sevenSegmentDisplaySetFrequency(frequency: number) {
if (!sevenSegmentDisplayHub.value || !sevenSegmentDisplayHubProxy.value)
return;
if (sevenSegmentDisplayHub.value.state === HubConnectionState.Disconnected)
await sevenSegmentDisplayHub.value.start();
await sevenSegmentDisplayHubProxy.value.setFrequency(frequency);
}
async function sevenSegmentDisplayGetStatus() {
if (!sevenSegmentDisplayHub.value || !sevenSegmentDisplayHubProxy.value)
return;
if (sevenSegmentDisplayHub.value.state === HubConnectionState.Disconnected)
await sevenSegmentDisplayHub.value.start();
return await sevenSegmentDisplayHubProxy.value.getStatus();
}
async function handleSevenSegmentDisplayOnReceive(msg: string) {
const bytes = base64ToArrayBuffer(msg);
sevenSegmentDisplayData.value = new Uint8Array(bytes);
}
onMounted(async () => {
// 每次挂载都重新创建连接
sevenSegmentDisplayHub.value =
AuthManager.createHubConnection("DigitalTubesHub");
sevenSegmentDisplayHubProxy.value = getHubProxyFactory(
"IDigitalTubesHub",
).createHubProxy(sevenSegmentDisplayHub.value);
getReceiverRegister("IDigitalTubesReceiver").register(
sevenSegmentDisplayHub.value,
{
onReceive: handleSevenSegmentDisplayOnReceive,
},
);
});
onUnmounted(() => {
// 断开连接,清理资源
if (sevenSegmentDisplayHub.value) {
sevenSegmentDisplayHub.value.stop();
sevenSegmentDisplayHub.value = undefined;
sevenSegmentDisplayHubProxy.value = undefined;
}
});
return {
boardAddr,
boardPort,
setMatrixKey,
// Jtag
enableJtagBoundaryScan,
jtagBoundaryScanSetOnOff,
jtagBitstream,
jtagBoundaryScanFreq,
jtagUserBitstreams,
jtagUploadBitstream,
jtagDownloadBitstream,
jtagGetIDCode,
jtagSetSpeed,
// Matrix Key
enableMatrixKey,
matrixKeyStates,
matrixKeypadClientMutex,
matrixKeypadEnable,
matrixKeypadSetKeyStates,
// Power
enablePower,
powerClientMutex,
powerSetOnOff,
// Seven Segment Display
enableSevenSegmentDisplay,
sevenSegmentDisplayData,
sevenSegmentDisplayFrequency,
sevenSegmentDisplaySetOnOff,
sevenSegmentDisplaySetFrequency,
sevenSegmentDisplayGetStatus,
};
});