feat: 使用SignalR来控制jtag边界扫描
This commit is contained in:
122
src/APIClient.ts
122
src/APIClient.ts
@@ -23,6 +23,50 @@ export class Client {
|
||||
|
||||
}
|
||||
|
||||
getSignalrDevSpec_json( cancelToken?: CancelToken): Promise<void> {
|
||||
let url_ = this.baseUrl + "/signalr-dev/spec.json";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: AxiosRequestConfig = {
|
||||
method: "GET",
|
||||
url: url_,
|
||||
headers: {
|
||||
},
|
||||
cancelToken
|
||||
};
|
||||
|
||||
return this.instance.request(options_).catch((_error: any) => {
|
||||
if (isAxiosError(_error) && _error.response) {
|
||||
return _error.response;
|
||||
} else {
|
||||
throw _error;
|
||||
}
|
||||
}).then((_response: AxiosResponse) => {
|
||||
return this.processGetSignalrDevSpec_json(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processGetSignalrDevSpec_json(response: AxiosResponse): Promise<void> {
|
||||
const status = response.status;
|
||||
let _headers: any = {};
|
||||
if (response.headers && typeof response.headers === "object") {
|
||||
for (const k in response.headers) {
|
||||
if (response.headers.hasOwnProperty(k)) {
|
||||
_headers[k] = response.headers[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (status === 200) {
|
||||
const _responseText = response.data;
|
||||
return Promise.resolve<void>(null as any);
|
||||
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
const _responseText = response.data;
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
}
|
||||
return Promise.resolve<void>(null as any);
|
||||
}
|
||||
|
||||
getGetAPIClientCode( cancelToken?: CancelToken): Promise<void> {
|
||||
let url_ = this.baseUrl + "/GetAPIClientCode";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
@@ -7239,20 +7283,15 @@ export enum CaptureMode {
|
||||
|
||||
/** 调试器整体配置信息 */
|
||||
export class DebuggerConfig implements IDebuggerConfig {
|
||||
/** 时钟频率
|
||||
*/
|
||||
/** 时钟频率 */
|
||||
clkFreq!: number;
|
||||
/** 总端口数量
|
||||
*/
|
||||
/** 总端口数量 */
|
||||
totalPortNum!: number;
|
||||
/** 捕获深度(采样点数)
|
||||
*/
|
||||
/** 捕获深度(采样点数) */
|
||||
captureDepth!: number;
|
||||
/** 触发器数量
|
||||
*/
|
||||
/** 触发器数量 */
|
||||
triggerNum!: number;
|
||||
/** 所有信号通道的配置信息
|
||||
*/
|
||||
/** 所有信号通道的配置信息 */
|
||||
channelConfigs!: ChannelConfig[];
|
||||
|
||||
constructor(data?: IDebuggerConfig) {
|
||||
@@ -7305,42 +7344,31 @@ export class DebuggerConfig implements IDebuggerConfig {
|
||||
|
||||
/** 调试器整体配置信息 */
|
||||
export interface IDebuggerConfig {
|
||||
/** 时钟频率
|
||||
*/
|
||||
/** 时钟频率 */
|
||||
clkFreq: number;
|
||||
/** 总端口数量
|
||||
*/
|
||||
/** 总端口数量 */
|
||||
totalPortNum: number;
|
||||
/** 捕获深度(采样点数)
|
||||
*/
|
||||
/** 捕获深度(采样点数) */
|
||||
captureDepth: number;
|
||||
/** 触发器数量
|
||||
*/
|
||||
/** 触发器数量 */
|
||||
triggerNum: number;
|
||||
/** 所有信号通道的配置信息
|
||||
*/
|
||||
/** 所有信号通道的配置信息 */
|
||||
channelConfigs: ChannelConfig[];
|
||||
}
|
||||
|
||||
/** 表示单个信号通道的配置信息 */
|
||||
export class ChannelConfig implements IChannelConfig {
|
||||
/** 通道名称
|
||||
*/
|
||||
/** 通道名称 */
|
||||
name!: string;
|
||||
/** 通道显示颜色(如前端波形显示用)
|
||||
*/
|
||||
/** 通道显示颜色(如前端波形显示用) */
|
||||
color!: string;
|
||||
/** 通道信号线宽度(位数)
|
||||
*/
|
||||
/** 通道信号线宽度(位数) */
|
||||
wireWidth!: number;
|
||||
/** 信号线在父端口中的起始索引(bit)
|
||||
*/
|
||||
/** 信号线在父端口中的起始索引(bit) */
|
||||
wireStartIndex!: number;
|
||||
/** 父端口编号
|
||||
*/
|
||||
/** 父端口编号 */
|
||||
parentPort!: number;
|
||||
/** 捕获模式(如上升沿、下降沿等)
|
||||
*/
|
||||
/** 捕获模式(如上升沿、下降沿等) */
|
||||
mode!: CaptureMode;
|
||||
|
||||
constructor(data?: IChannelConfig) {
|
||||
@@ -7384,33 +7412,25 @@ export class ChannelConfig implements IChannelConfig {
|
||||
|
||||
/** 表示单个信号通道的配置信息 */
|
||||
export interface IChannelConfig {
|
||||
/** 通道名称
|
||||
*/
|
||||
/** 通道名称 */
|
||||
name: string;
|
||||
/** 通道显示颜色(如前端波形显示用)
|
||||
*/
|
||||
/** 通道显示颜色(如前端波形显示用) */
|
||||
color: string;
|
||||
/** 通道信号线宽度(位数)
|
||||
*/
|
||||
/** 通道信号线宽度(位数) */
|
||||
wireWidth: number;
|
||||
/** 信号线在父端口中的起始索引(bit)
|
||||
*/
|
||||
/** 信号线在父端口中的起始索引(bit) */
|
||||
wireStartIndex: number;
|
||||
/** 父端口编号
|
||||
*/
|
||||
/** 父端口编号 */
|
||||
parentPort: number;
|
||||
/** 捕获模式(如上升沿、下降沿等)
|
||||
*/
|
||||
/** 捕获模式(如上升沿、下降沿等) */
|
||||
mode: CaptureMode;
|
||||
}
|
||||
|
||||
/** 单个通道的捕获数据 */
|
||||
export class ChannelCaptureData implements IChannelCaptureData {
|
||||
/** 通道名称
|
||||
*/
|
||||
/** 通道名称 */
|
||||
name!: string;
|
||||
/** 通道捕获到的数据(Base64编码的UInt32数组)
|
||||
*/
|
||||
/** 通道捕获到的数据(Base64编码的UInt32数组) */
|
||||
data!: string;
|
||||
|
||||
constructor(data?: IChannelCaptureData) {
|
||||
@@ -7446,11 +7466,9 @@ export class ChannelCaptureData implements IChannelCaptureData {
|
||||
|
||||
/** 单个通道的捕获数据 */
|
||||
export interface IChannelCaptureData {
|
||||
/** 通道名称
|
||||
*/
|
||||
/** 通道名称 */
|
||||
name: string;
|
||||
/** 通道捕获到的数据(Base64编码的UInt32数组)
|
||||
*/
|
||||
/** 通道捕获到的数据(Base64编码的UInt32数组) */
|
||||
data: string;
|
||||
}
|
||||
|
||||
|
||||
118
src/TypedSignalR.Client/index.ts
Normal file
118
src/TypedSignalR.Client/index.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
/* THIS (.ts) FILE IS GENERATED BY TypedSignalR.Client.TypeScript */
|
||||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
// @ts-nocheck
|
||||
import type { HubConnection, IStreamResult, Subject } from '@microsoft/signalr';
|
||||
import type { IJtagHub, IJtagReceiver } from './server.Hubs.JtagHub';
|
||||
|
||||
|
||||
// components
|
||||
|
||||
export type Disposable = {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export type HubProxyFactory<T> = {
|
||||
createHubProxy(connection: HubConnection): T;
|
||||
}
|
||||
|
||||
export type ReceiverRegister<T> = {
|
||||
register(connection: HubConnection, receiver: T): Disposable;
|
||||
}
|
||||
|
||||
type ReceiverMethod = {
|
||||
methodName: string,
|
||||
method: (...args: any[]) => void
|
||||
}
|
||||
|
||||
class ReceiverMethodSubscription implements Disposable {
|
||||
|
||||
public constructor(
|
||||
private connection: HubConnection,
|
||||
private receiverMethod: ReceiverMethod[]) {
|
||||
}
|
||||
|
||||
public readonly dispose = () => {
|
||||
for (const it of this.receiverMethod) {
|
||||
this.connection.off(it.methodName, it.method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// API
|
||||
|
||||
export type HubProxyFactoryProvider = {
|
||||
(hubType: "IJtagHub"): HubProxyFactory<IJtagHub>;
|
||||
}
|
||||
|
||||
export const getHubProxyFactory = ((hubType: string) => {
|
||||
if(hubType === "IJtagHub") {
|
||||
return IJtagHub_HubProxyFactory.Instance;
|
||||
}
|
||||
}) as HubProxyFactoryProvider;
|
||||
|
||||
export type ReceiverRegisterProvider = {
|
||||
(receiverType: "IJtagReceiver"): ReceiverRegister<IJtagReceiver>;
|
||||
}
|
||||
|
||||
export const getReceiverRegister = ((receiverType: string) => {
|
||||
if(receiverType === "IJtagReceiver") {
|
||||
return IJtagReceiver_Binder.Instance;
|
||||
}
|
||||
}) as ReceiverRegisterProvider;
|
||||
|
||||
// HubProxy
|
||||
|
||||
class IJtagHub_HubProxyFactory implements HubProxyFactory<IJtagHub> {
|
||||
public static Instance = new IJtagHub_HubProxyFactory();
|
||||
|
||||
private constructor() {
|
||||
}
|
||||
|
||||
public readonly createHubProxy = (connection: HubConnection): IJtagHub => {
|
||||
return new IJtagHub_HubProxy(connection);
|
||||
}
|
||||
}
|
||||
|
||||
class IJtagHub_HubProxy implements IJtagHub {
|
||||
|
||||
public constructor(private connection: HubConnection) {
|
||||
}
|
||||
|
||||
public readonly setBoundaryScanFreq = async (freq: number): Promise<boolean> => {
|
||||
return await this.connection.invoke("SetBoundaryScanFreq", freq);
|
||||
}
|
||||
|
||||
public readonly startBoundaryScan = async (freq: number): Promise<boolean> => {
|
||||
return await this.connection.invoke("StartBoundaryScan", freq);
|
||||
}
|
||||
|
||||
public readonly stopBoundaryScan = async (): Promise<boolean> => {
|
||||
return await this.connection.invoke("StopBoundaryScan");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Receiver
|
||||
|
||||
class IJtagReceiver_Binder implements ReceiverRegister<IJtagReceiver> {
|
||||
|
||||
public static Instance = new IJtagReceiver_Binder();
|
||||
|
||||
private constructor() {
|
||||
}
|
||||
|
||||
public readonly register = (connection: HubConnection, receiver: IJtagReceiver): Disposable => {
|
||||
|
||||
const __onReceiveBoundaryScanData = (...args: [Partial<Record<string, boolean>>]) => receiver.onReceiveBoundaryScanData(...args);
|
||||
|
||||
connection.on("OnReceiveBoundaryScanData", __onReceiveBoundaryScanData);
|
||||
|
||||
const methodList: ReceiverMethod[] = [
|
||||
{ methodName: "OnReceiveBoundaryScanData", method: __onReceiveBoundaryScanData }
|
||||
]
|
||||
|
||||
return new ReceiverMethodSubscription(connection, methodList);
|
||||
}
|
||||
}
|
||||
|
||||
31
src/TypedSignalR.Client/server.Hubs.JtagHub.ts
Normal file
31
src/TypedSignalR.Client/server.Hubs.JtagHub.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/* THIS (.ts) FILE IS GENERATED BY TypedSignalR.Client.TypeScript */
|
||||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
// @ts-nocheck
|
||||
import type { IStreamResult, Subject } from '@microsoft/signalr';
|
||||
|
||||
export type IJtagHub = {
|
||||
/**
|
||||
* @param freq Transpiled from int
|
||||
* @returns Transpiled from System.Threading.Tasks.Task<bool>
|
||||
*/
|
||||
setBoundaryScanFreq(freq: number): Promise<boolean>;
|
||||
/**
|
||||
* @param freq Transpiled from int
|
||||
* @returns Transpiled from System.Threading.Tasks.Task<bool>
|
||||
*/
|
||||
startBoundaryScan(freq: number): Promise<boolean>;
|
||||
/**
|
||||
* @returns Transpiled from System.Threading.Tasks.Task<bool>
|
||||
*/
|
||||
stopBoundaryScan(): Promise<boolean>;
|
||||
}
|
||||
|
||||
export type IJtagReceiver = {
|
||||
/**
|
||||
* @param msg Transpiled from System.Collections.Generic.Dictionary<string, bool>
|
||||
* @returns Transpiled from System.Threading.Tasks.Task
|
||||
*/
|
||||
onReceiveBoundaryScanData(msg: Partial<Record<string, boolean>>): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<template>
|
||||
<button class="btn transition-transform duration-150 ease-in-out hover:scale-120">
|
||||
Button A
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
@import "@/assets/main.css";
|
||||
</style>
|
||||
@@ -1,162 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
class="card card-dash shadow-xl h-screen"
|
||||
:class="[sidebar.isClose ? 'w-31' : 'w-80']"
|
||||
>
|
||||
<div
|
||||
class="card-body flex relative transition-all duration-500 ease-in-out"
|
||||
>
|
||||
<!-- Avatar and Name -->
|
||||
<div class="relative" :class="sidebar.isClose ? 'h-50' : 'h-20'">
|
||||
<!-- Img -->
|
||||
<div
|
||||
class="avatar h-10 fixed top-10"
|
||||
:class="sidebar.isClose ? 'left-10' : 'left-7'"
|
||||
>
|
||||
<div class="rounded-full">
|
||||
<img src="../assets/user.svg" alt="User" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- Text -->
|
||||
<Transition>
|
||||
<div v-if="!sidebar.isClose" class="mx-5 grow fixed left-20 top-11">
|
||||
<label class="text-2xl">用户名</label>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
|
||||
<!-- Toggle Button -->
|
||||
<button
|
||||
class="btn btn-square rounded-lg p-2 m-3 fixed"
|
||||
:class="sidebar.isClose ? 'left-7 top-23' : 'left-60 top-7'"
|
||||
@click="sidebar.toggleSidebar"
|
||||
>
|
||||
<svg
|
||||
t="1741694970690"
|
||||
:class="sidebar.isClose ? 'rotate-0' : 'rotate-540'"
|
||||
class="icon"
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="4546"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="200"
|
||||
height="200"
|
||||
>
|
||||
<path
|
||||
d="M803.758 514.017c-0.001-0.311-0.013-0.622-0.018-0.933-0.162-23.974-9.386-47.811-27.743-65.903-0.084-0.082-0.172-0.157-0.256-0.239-0.154-0.154-0.296-0.315-0.451-0.468L417.861 94.096c-37.685-37.153-99.034-37.476-136.331-0.718-37.297 36.758-36.979 97.231 0.707 134.384l290.361 286.257-290.362 286.257c-37.685 37.153-38.004 97.625-0.707 134.383 37.297 36.758 98.646 36.435 136.331-0.718l357.43-352.378c0.155-0.153 0.297-0.314 0.451-0.468 0.084-0.082 0.172-0.157 0.256-0.239 18.354-18.089 27.578-41.922 27.743-65.892 0.004-0.315 0.017-0.631 0.018-0.947z"
|
||||
:fill="theme.isLightTheme() ? '#828282' : '#C0C3C8'"
|
||||
p-id="4547"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<ul class="menu h-full w-full">
|
||||
<li v-for="item in props.items" class="text-xl my-1">
|
||||
<a @click="router.push(item.page)">
|
||||
<svg
|
||||
t="1741694797806"
|
||||
class="icon h-[1.5em] w-[1.5em] opacity-50 mx-1"
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="2622"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="200"
|
||||
height="200"
|
||||
>
|
||||
<path
|
||||
d="M192 192m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0Z"
|
||||
p-id="2623"
|
||||
></path>
|
||||
<path
|
||||
d="M192 512m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0Z"
|
||||
p-id="2624"
|
||||
></path>
|
||||
<path
|
||||
d="M192 832m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0Z"
|
||||
p-id="2625"
|
||||
></path>
|
||||
<path
|
||||
d="M864 160H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32zM864 480H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32zM864 800H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32z"
|
||||
p-id="2626"
|
||||
></path>
|
||||
</svg>
|
||||
<Transition>
|
||||
<p class="break-keep" v-if="!sidebar.isClose">{{ item.text }}</p>
|
||||
</Transition>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<ul class="menu w-full">
|
||||
<li>
|
||||
<a @click="theme.toggleTheme" class="text-xl">
|
||||
<ThemeControlButton />
|
||||
<Transition>
|
||||
<p v-if="!sidebar.isClose" class="break-keep">改变主题</p>
|
||||
</Transition>
|
||||
<Transition>
|
||||
<ThemeControlToggle v-if="!sidebar.isClose" />
|
||||
</Transition>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import iconMenu from "../assets/menu.svg";
|
||||
import "../router";
|
||||
import { useThemeStore } from "@/stores/theme";
|
||||
import { useSidebarStore } from "@/stores/sidebar";
|
||||
import ThemeControlButton from "./ThemeControlButton.vue";
|
||||
import ThemeControlToggle from "./ThemeControlToggle.vue";
|
||||
import router from "../router";
|
||||
|
||||
const theme = useThemeStore();
|
||||
const sidebar = useSidebarStore();
|
||||
|
||||
type Item = {
|
||||
id: number;
|
||||
icon: string;
|
||||
text: string;
|
||||
page: string;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
items?: Array<Item>;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
items: () => [
|
||||
{ id: 1, icon: iconMenu, text: "btn1", page: "/" },
|
||||
{ id: 2, icon: iconMenu, text: "btn2", page: "/" },
|
||||
{ id: 3, icon: iconMenu, text: "btn3", page: "/" },
|
||||
{ id: 4, icon: iconMenu, text: "btn4", page: "/" },
|
||||
],
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
@reference "../assets/main.css";
|
||||
|
||||
* {
|
||||
@apply transition-all duration-500 ease-in-out;
|
||||
}
|
||||
|
||||
.v-enter-active,
|
||||
.v-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.v-enter-from,
|
||||
.v-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -8,20 +8,35 @@
|
||||
<p>
|
||||
IDCode: 0x{{ jtagIDCode.toString(16).padStart(8, "0").toUpperCase() }}
|
||||
</p>
|
||||
<button class="btn btn-circle w-6 h-6" :disabled="isGettingIDCode" :onclick="getIDCode">
|
||||
<RefreshCcwIcon class="icon" :class="{ 'animate-spin': isGettingIDCode }" />
|
||||
<button
|
||||
class="btn btn-circle w-6 h-6"
|
||||
:disabled="isGettingIDCode"
|
||||
:onclick="getIDCode"
|
||||
>
|
||||
<RefreshCcwIcon
|
||||
class="icon"
|
||||
:class="{ 'animate-spin': isGettingIDCode }"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<UploadCard class="bg-base-200" :upload-event="eqps.jtagUploadBitstream"
|
||||
:download-event="eqps.jtagDownloadBitstream" :bitstream-file="eqps.jtagBitstream"
|
||||
@update:bitstream-file="handleBitstreamChange">
|
||||
<UploadCard
|
||||
class="bg-base-200"
|
||||
:upload-event="eqps.jtagUploadBitstream"
|
||||
:download-event="eqps.jtagDownloadBitstream"
|
||||
:bitstream-file="eqps.jtagBitstream"
|
||||
@update:bitstream-file="handleBitstreamChange"
|
||||
>
|
||||
</UploadCard>
|
||||
<div class="divider"></div>
|
||||
<div class="w-full">
|
||||
<legend class="fieldset-legend text-sm mb-0.3">Jtag运行频率</legend>
|
||||
<select class="select w-full" @change="handleSelectJtagSpeed" :value="props.jtagFreq">
|
||||
<select
|
||||
class="select w-full"
|
||||
@change="handleSelectJtagSpeed"
|
||||
:value="props.jtagFreq"
|
||||
>
|
||||
<option v-for="option in selectJtagSpeedOptions" :value="option.id">
|
||||
{{ option.text }}
|
||||
</option>
|
||||
@@ -30,12 +45,23 @@
|
||||
<div class="flex flex-row items-center">
|
||||
<fieldset class="fieldset w-70">
|
||||
<legend class="fieldset-legend text-sm">边界扫描刷新率 / Hz</legend>
|
||||
<input type="number" class="input validator" required placeholder="Type a number between 1 to 1000" min="1"
|
||||
max="1000" v-model="jtagBoundaryScanFreq" title="Type a number between 1 to 1000" />
|
||||
<input
|
||||
type="number"
|
||||
class="input validator"
|
||||
required
|
||||
placeholder="Type a number between 1 to 1000"
|
||||
min="1"
|
||||
max="1000"
|
||||
v-model="jtagBoundaryScanFreq"
|
||||
title="Type a number between 1 to 1000"
|
||||
/>
|
||||
<p class="validator-hint">输入一个1 ~ 1000的数</p>
|
||||
</fieldset>
|
||||
<button class="btn btn-primary grow mx-4" :class="eqps.enableJtagBoundaryScan ? '' : 'btn-soft'"
|
||||
:onclick="toggleJtagBoundaryScan">
|
||||
<button
|
||||
class="btn btn-primary grow mx-4"
|
||||
:class="eqps.enableJtagBoundaryScan ? '' : 'btn-soft'"
|
||||
:onclick="toggleJtagBoundaryScan"
|
||||
>
|
||||
{{ eqps.enableJtagBoundaryScan ? "关闭边界扫描" : "启动边界扫描" }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -43,8 +69,12 @@
|
||||
<h1 class="font-bold text-center text-2xl">外设</h1>
|
||||
<div class="flex flex-row justify-center">
|
||||
<div class="flex flex-row">
|
||||
<input type="checkbox" class="checkbox" :checked="eqps.enableMatrixKey"
|
||||
@change="handleMatrixkeyCheckboxChange" />
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
:checked="eqps.enableMatrixKey"
|
||||
@change="handleMatrixkeyCheckboxChange"
|
||||
/>
|
||||
<p class="mx-2">启用矩阵键盘</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -117,7 +147,7 @@ async function handleMatrixkeyCheckboxChange(event: Event) {
|
||||
}
|
||||
|
||||
async function toggleJtagBoundaryScan() {
|
||||
eqps.enableJtagBoundaryScan = !eqps.enableJtagBoundaryScan;
|
||||
eqps.jtagBoundaryScanSetOnOff(!eqps.enableJtagBoundaryScan);
|
||||
}
|
||||
|
||||
const isGettingIDCode = ref(false);
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
import { ref, computed, reactive } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import { isBoolean } from 'lodash';
|
||||
import { ref, computed, reactive } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
import { isBoolean } from "lodash";
|
||||
|
||||
// 约束电平状态类型
|
||||
export type ConstraintLevel = 'high' | 'low' | 'undefined';
|
||||
|
||||
export const useConstraintsStore = defineStore('constraints', () => {
|
||||
export type ConstraintLevel = "high" | "low" | "undefined";
|
||||
|
||||
export const useConstraintsStore = defineStore("constraints", () => {
|
||||
// 约束状态存储
|
||||
const constraintStates = reactive<Record<string, ConstraintLevel>>({});
|
||||
|
||||
// 约束颜色映射
|
||||
const constraintColors = {
|
||||
high: '#ff3333', // 高电平为红色
|
||||
low: '#3333ff', // 低电平为蓝色
|
||||
undefined: '#999999' // 未定义为灰色
|
||||
high: "#ff3333", // 高电平为红色
|
||||
low: "#3333ff", // 低电平为蓝色
|
||||
undefined: "#999999", // 未定义为灰色
|
||||
};
|
||||
|
||||
// 获取约束状态
|
||||
function getConstraintState(constraint: string): ConstraintLevel {
|
||||
if (!constraint) return 'undefined';
|
||||
return constraintStates[constraint] || 'undefined';
|
||||
if (!constraint) return "undefined";
|
||||
return constraintStates[constraint] || "undefined";
|
||||
}
|
||||
|
||||
// 设置约束状态
|
||||
@@ -30,7 +29,9 @@ export const useConstraintsStore = defineStore('constraints', () => {
|
||||
}
|
||||
|
||||
// 批量设置约束状态
|
||||
function batchSetConstraintStates(states: Record<string, ConstraintLevel> | Record<string, boolean>) {
|
||||
function batchSetConstraintStates(
|
||||
states: Record<string, ConstraintLevel> | Partial<Record<string, boolean>>,
|
||||
) {
|
||||
// 收集发生变化的约束
|
||||
const changedConstraints: [string, ConstraintLevel][] = [];
|
||||
|
||||
@@ -38,6 +39,8 @@ export const useConstraintsStore = defineStore('constraints', () => {
|
||||
Object.entries(states).forEach(([constraint, level]) => {
|
||||
if (isBoolean(level)) {
|
||||
level = level ? "high" : "low";
|
||||
} else {
|
||||
level = "low";
|
||||
}
|
||||
|
||||
if (constraintStates[constraint] !== level) {
|
||||
@@ -48,7 +51,7 @@ export const useConstraintsStore = defineStore('constraints', () => {
|
||||
|
||||
// 通知所有变化
|
||||
changedConstraints.forEach(([constraint, level]) => {
|
||||
stateChangeCallbacks.forEach(callback => callback(constraint, level));
|
||||
stateChangeCallbacks.forEach((callback) => callback(constraint, level));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -60,7 +63,7 @@ export const useConstraintsStore = defineStore('constraints', () => {
|
||||
|
||||
// 清除所有约束状态
|
||||
function clearAllConstraintStates() {
|
||||
Object.keys(constraintStates).forEach(key => {
|
||||
Object.keys(constraintStates).forEach((key) => {
|
||||
delete constraintStates[key];
|
||||
});
|
||||
}
|
||||
@@ -71,9 +74,14 @@ export const useConstraintsStore = defineStore('constraints', () => {
|
||||
}
|
||||
|
||||
// 注册约束状态变化回调
|
||||
const stateChangeCallbacks: ((constraint: string, level: ConstraintLevel) => void)[] = [];
|
||||
const stateChangeCallbacks: ((
|
||||
constraint: string,
|
||||
level: ConstraintLevel,
|
||||
) => void)[] = [];
|
||||
|
||||
function onConstraintStateChange(callback: (constraint: string, level: ConstraintLevel) => void) {
|
||||
function onConstraintStateChange(
|
||||
callback: (constraint: string, level: ConstraintLevel) => void,
|
||||
) {
|
||||
stateChangeCallbacks.push(callback);
|
||||
return () => {
|
||||
const index = stateChangeCallbacks.indexOf(callback);
|
||||
@@ -86,7 +94,7 @@ export const useConstraintsStore = defineStore('constraints', () => {
|
||||
// 触发约束变化
|
||||
function notifyConstraintChange(constraint: string, level: ConstraintLevel) {
|
||||
setConstraintState(constraint, level);
|
||||
stateChangeCallbacks.forEach(callback => callback(constraint, level));
|
||||
stateChangeCallbacks.forEach((callback) => callback(constraint, level));
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -98,6 +106,5 @@ export const useConstraintsStore = defineStore('constraints', () => {
|
||||
getAllConstraintStates,
|
||||
onConstraintStateChange,
|
||||
notifyConstraintChange,
|
||||
}
|
||||
})
|
||||
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { ref, reactive, watchPostEffect } from "vue";
|
||||
import { ref, reactive, watchPostEffect, onMounted, onUnmounted } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
import { useLocalStorage } from "@vueuse/core";
|
||||
import { isString, toNumber } from "lodash";
|
||||
import { isString, toNumber, type Dictionary } from "lodash";
|
||||
import z from "zod";
|
||||
import { isNumber } from "mathjs";
|
||||
import { JtagClient, MatrixKeyClient, PowerClient } from "@/APIClient";
|
||||
import { Mutex, withTimeout } from "async-mutex";
|
||||
import { useConstraintsStore } from "@/stores/constraints";
|
||||
import { useDialogStore } from "./dialog";
|
||||
import { toFileParameterOrUndefined } from "@/utils/Common";
|
||||
import { AuthManager } from "@/utils/AuthManager";
|
||||
import { HubConnectionBuilder } from "@microsoft/signalr";
|
||||
import { getHubProxyFactory, getReceiverRegister } from "@/TypedSignalR.Client";
|
||||
|
||||
export const useEquipments = defineStore("equipments", () => {
|
||||
// Global Stores
|
||||
@@ -22,13 +23,28 @@ export const useEquipments = defineStore("equipments", () => {
|
||||
// Jtag
|
||||
const jtagBitstream = ref<File>();
|
||||
const jtagBoundaryScanFreq = ref(100);
|
||||
const jtagBoundaryScanErrorCount = ref(0); // 边界扫描连续错误计数
|
||||
const maxJtagBoundaryScanErrors = 5; // 最大允许连续错误次数
|
||||
const jtagClientMutex = withTimeout(
|
||||
new Mutex(),
|
||||
1000,
|
||||
new Error("JtagClient Mutex Timeout!"),
|
||||
);
|
||||
const jtagHubConnection = new HubConnectionBuilder()
|
||||
.withUrl("/hubs/JtagHub")
|
||||
.withAutomaticReconnect()
|
||||
.build();
|
||||
const jtagHubProxy =
|
||||
getHubProxyFactory("IJtagHub").createHubProxy(jtagHubConnection);
|
||||
const jtagHubSubscription = getReceiverRegister("IJtagReceiver").register(
|
||||
jtagHubConnection,
|
||||
{
|
||||
onReceiveBoundaryScanData: async (msg) => {
|
||||
constrainsts.batchSetConstraintStates(msg);
|
||||
},
|
||||
},
|
||||
);
|
||||
onMounted(() => {
|
||||
jtagHubConnection.start();
|
||||
});
|
||||
|
||||
// Matrix Key
|
||||
const matrixKeyStates = reactive(new Array<boolean>(16).fill(false));
|
||||
@@ -50,41 +66,6 @@ export const useEquipments = defineStore("equipments", () => {
|
||||
const enableMatrixKey = ref(false);
|
||||
const enablePower = ref(false);
|
||||
|
||||
// Watch
|
||||
watchPostEffect(async () => {
|
||||
if (true === enableJtagBoundaryScan.value) {
|
||||
// 重新启用时重置错误计数器
|
||||
jtagBoundaryScanErrorCount.value = 0;
|
||||
jtagBoundaryScan();
|
||||
}
|
||||
});
|
||||
|
||||
// Parse and Set
|
||||
function setAddr(address: string | undefined): boolean {
|
||||
if (isString(address) && z.string().ip("4").safeParse(address).success) {
|
||||
boardAddr.value = address;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function setPort(port: string | number | undefined): boolean {
|
||||
if (isString(port) && port.length != 0) {
|
||||
const portNumber = toNumber(port);
|
||||
if (z.number().nonnegative().max(65535).safeParse(portNumber).success) {
|
||||
boardPort.value = portNumber;
|
||||
return true;
|
||||
}
|
||||
} else if (isNumber(port)) {
|
||||
if (z.number().nonnegative().max(65535).safeParse(port).success) {
|
||||
boardPort.value = port;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function setMatrixKey(
|
||||
keyNum: number | string | undefined,
|
||||
keyValue: boolean,
|
||||
@@ -105,38 +86,12 @@ export const useEquipments = defineStore("equipments", () => {
|
||||
return false;
|
||||
}
|
||||
|
||||
async function jtagBoundaryScan() {
|
||||
const release = await jtagClientMutex.acquire();
|
||||
try {
|
||||
// 自动开启电源
|
||||
await powerSetOnOff(true);
|
||||
|
||||
const jtagClient = AuthManager.createAuthenticatedJtagClient();
|
||||
const portStates = await jtagClient.boundaryScanLogicalPorts(
|
||||
boardAddr.value,
|
||||
boardPort.value,
|
||||
);
|
||||
|
||||
constrainsts.batchSetConstraintStates(portStates);
|
||||
|
||||
// 扫描成功,重置错误计数器
|
||||
jtagBoundaryScanErrorCount.value = 0;
|
||||
} catch (error) {
|
||||
jtagBoundaryScanErrorCount.value++;
|
||||
|
||||
console.error(`边界扫描错误 (${jtagBoundaryScanErrorCount.value}/${maxJtagBoundaryScanErrors}):`, error);
|
||||
|
||||
// 如果错误次数超过最大允许次数,才停止扫描并显示错误
|
||||
if (jtagBoundaryScanErrorCount.value >= maxJtagBoundaryScanErrors) {
|
||||
dialog.error("边界扫描发生连续错误,已自动停止");
|
||||
enableJtagBoundaryScan.value = false;
|
||||
jtagBoundaryScanErrorCount.value = 0; // 重置错误计数器
|
||||
}
|
||||
} finally {
|
||||
release();
|
||||
|
||||
if (enableJtagBoundaryScan.value)
|
||||
setTimeout(jtagBoundaryScan, 1000 / jtagBoundaryScanFreq.value);
|
||||
async function jtagBoundaryScanSetOnOff(enable: boolean) {
|
||||
enableJtagBoundaryScan.value = enable;
|
||||
if (enable) {
|
||||
jtagHubProxy.startBoundaryScan(jtagBoundaryScanFreq.value);
|
||||
} else {
|
||||
jtagHubProxy.stopBoundaryScan();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +99,7 @@ export const useEquipments = defineStore("equipments", () => {
|
||||
try {
|
||||
// 自动开启电源
|
||||
await powerSetOnOff(true);
|
||||
|
||||
|
||||
const jtagClient = AuthManager.createAuthenticatedJtagClient();
|
||||
const resp = await jtagClient.uploadBitstream(
|
||||
boardAddr.value,
|
||||
@@ -163,7 +118,7 @@ export const useEquipments = defineStore("equipments", () => {
|
||||
try {
|
||||
// 自动开启电源
|
||||
await powerSetOnOff(true);
|
||||
|
||||
|
||||
const jtagClient = AuthManager.createAuthenticatedJtagClient();
|
||||
const resp = await jtagClient.downloadBitstream(
|
||||
boardAddr.value,
|
||||
@@ -184,7 +139,7 @@ export const useEquipments = defineStore("equipments", () => {
|
||||
try {
|
||||
// 自动开启电源
|
||||
await powerSetOnOff(true);
|
||||
|
||||
|
||||
const jtagClient = AuthManager.createAuthenticatedJtagClient();
|
||||
const resp = await jtagClient.getDeviceIDCode(
|
||||
boardAddr.value,
|
||||
@@ -204,7 +159,7 @@ export const useEquipments = defineStore("equipments", () => {
|
||||
try {
|
||||
// 自动开启电源
|
||||
await powerSetOnOff(true);
|
||||
|
||||
|
||||
const jtagClient = AuthManager.createAuthenticatedJtagClient();
|
||||
const resp = await jtagClient.setSpeed(
|
||||
boardAddr.value,
|
||||
@@ -224,7 +179,8 @@ export const useEquipments = defineStore("equipments", () => {
|
||||
const release = await matrixKeypadClientMutex.acquire();
|
||||
console.log("set Key !!!!!!!!!!!!");
|
||||
try {
|
||||
const matrixKeypadClient = AuthManager.createAuthenticatedMatrixKeyClient();
|
||||
const matrixKeypadClient =
|
||||
AuthManager.createAuthenticatedMatrixKeyClient();
|
||||
const resp = await matrixKeypadClient.setMatrixKeyStatus(
|
||||
boardAddr.value,
|
||||
boardPort.value,
|
||||
@@ -243,7 +199,8 @@ export const useEquipments = defineStore("equipments", () => {
|
||||
const release = await matrixKeypadClientMutex.acquire();
|
||||
try {
|
||||
if (enable) {
|
||||
const matrixKeypadClient = AuthManager.createAuthenticatedMatrixKeyClient();
|
||||
const matrixKeypadClient =
|
||||
AuthManager.createAuthenticatedMatrixKeyClient();
|
||||
const resp = await matrixKeypadClient.enabelMatrixKey(
|
||||
boardAddr.value,
|
||||
boardPort.value,
|
||||
@@ -251,7 +208,8 @@ export const useEquipments = defineStore("equipments", () => {
|
||||
enableMatrixKey.value = resp;
|
||||
return resp;
|
||||
} else {
|
||||
const matrixKeypadClient = AuthManager.createAuthenticatedMatrixKeyClient();
|
||||
const matrixKeypadClient =
|
||||
AuthManager.createAuthenticatedMatrixKeyClient();
|
||||
const resp = await matrixKeypadClient.disableMatrixKey(
|
||||
boardAddr.value,
|
||||
boardPort.value,
|
||||
@@ -290,16 +248,13 @@ export const useEquipments = defineStore("equipments", () => {
|
||||
return {
|
||||
boardAddr,
|
||||
boardPort,
|
||||
setAddr,
|
||||
setPort,
|
||||
setMatrixKey,
|
||||
|
||||
// Jtag
|
||||
enableJtagBoundaryScan,
|
||||
jtagBoundaryScanSetOnOff,
|
||||
jtagBitstream,
|
||||
jtagBoundaryScanFreq,
|
||||
jtagBoundaryScanErrorCount,
|
||||
jtagClientMutex,
|
||||
jtagUploadBitstream,
|
||||
jtagDownloadBitstream,
|
||||
jtagGetIDCode,
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useSidebarStore = defineStore('sidebar', () => {
|
||||
const isClose = ref(false);
|
||||
|
||||
function closeSidebar() {
|
||||
isClose.value = true;
|
||||
console.info("Close sidebar");
|
||||
}
|
||||
|
||||
function openSidebar() {
|
||||
isClose.value = false;
|
||||
console.info("Open sidebar");
|
||||
}
|
||||
|
||||
function toggleSidebar() {
|
||||
if (isClose.value) {
|
||||
openSidebar();
|
||||
// themeSidebar.value = "card-dash sidebar-base sidebar-open"
|
||||
} else {
|
||||
closeSidebar();
|
||||
// themeSidebar.value = "card-dash sidebar-base sidebar-close"
|
||||
}
|
||||
}
|
||||
|
||||
return { isClose, closeSidebar, openSidebar, toggleSidebar }
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user