feat: 完成debugger前后端交互

This commit is contained in:
SikongJueluo 2025-07-29 19:10:21 +08:00
parent 23d4459406
commit 3da0f284f3
No known key found for this signature in database
5 changed files with 359 additions and 17 deletions

View File

@ -58,15 +58,20 @@ public class DebuggerController : ControllerBase
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<IActionResult> SetMode([FromBody] CaptureMode mode)
public async Task<IActionResult> SetMode(int channelNum, CaptureMode mode)
{
if (channelNum > 0x0F)
{
return BadRequest($"最多只能建立16个通道");
}
try
{
var debugger = GetDebugger();
if (debugger == null)
return BadRequest("用户未绑定有效的实验板");
var result = await debugger.SetMode(mode);
var result = await debugger.SetMode((byte)channelNum, mode);
if (!result.IsSuccessful)
{
logger.Error($"设置捕获模式失败: {result.Error}");

View File

@ -43,12 +43,12 @@ class DebuggerCmd
/// 启动触发器命令
/// </summary>
public const UInt32 Start = 0xFFFF_FFFF;
/// <summary>
/// 刷新命令
/// </summary>
public const UInt32 Fresh = 0x0000_0000;
/// <summary>
/// 清除信号标志命令
/// </summary>
@ -119,12 +119,18 @@ public class DebuggerClient
/// <summary>
/// 设置信号捕获模式
/// </summary>
/// <param name="channelNum">要设置的通道</param>
/// <param name="mode">要设置的捕获模式</param>
/// <returns>操作结果成功返回true失败返回错误信息</returns>
public async ValueTask<Result<bool>> SetMode(CaptureMode mode)
public async ValueTask<Result<bool>> SetMode(byte channelNum, CaptureMode mode)
{
if (channelNum > 0x0F)
{
return new(new ArgumentException($"Channel Num can't be over 16, but receive num: {channelNum}"));
}
UInt32 data = ((UInt32)mode);
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, DebuggerAddr.Mode, data, this.timeout);
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, DebuggerAddr.Mode + channelNum, data, this.timeout);
if (!ret.IsSuccessful)
{
logger.Error($"Failed to set mode: {ret.Error}");

View File

@ -1649,18 +1649,24 @@ export class DebuggerClient {
/**
*
* @param channelNum (optional)
* @param mode (optional)
*/
setMode(mode: string): Promise<boolean> {
let url_ = this.baseUrl + "/api/Debugger/SetMode";
setMode(channelNum: number | undefined, mode: CaptureMode | undefined): Promise<boolean> {
let url_ = this.baseUrl + "/api/Debugger/SetMode?";
if (channelNum === null)
throw new Error("The parameter 'channelNum' cannot be null.");
else if (channelNum !== undefined)
url_ += "channelNum=" + encodeURIComponent("" + channelNum) + "&";
if (mode === null)
throw new Error("The parameter 'mode' cannot be null.");
else if (mode !== undefined)
url_ += "mode=" + encodeURIComponent("" + mode) + "&";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(mode);
let options_: RequestInit = {
body: content_,
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
};
@ -5508,6 +5514,15 @@ export interface IArgumentException extends ISystemException {
paramName?: string | undefined;
}
/** 信号捕获模式枚举 */
export enum CaptureMode {
None = 0,
Logic0 = 1,
Logic1 = 2,
Rise = 3,
Fall = 4,
}
/** 逻辑分析仪运行状态枚举 */
export enum CaptureStatus {
None = 0,

View File

@ -12,6 +12,7 @@ import {
LogicAnalyzerClient,
NetConfigClient,
OscilloscopeApiClient,
DebuggerClient,
} from "@/APIClient";
// 支持的客户端类型联合类型
@ -28,7 +29,8 @@ type SupportedClient =
| LogicAnalyzerClient
| UDPClient
| NetConfigClient
| OscilloscopeApiClient;
| OscilloscopeApiClient
| DebuggerClient;
export class AuthManager {
// 存储token到localStorage
@ -168,6 +170,10 @@ export class AuthManager {
public static createAuthenticatedOscilloscopeApiClient(): OscilloscopeApiClient {
return AuthManager.createAuthenticatedClient(OscilloscopeApiClient);
}
public static createAuthenticatedDebuggerClient(): DebuggerClient {
return AuthManager.createAuthenticatedClient(DebuggerClient);
}
// 登录函数
public static async login(

View File

@ -1,11 +1,321 @@
<template>
<div>
<div class="card m-5 bg-base-200 shadow-2xl">
<WaveformDisplay />
<div class="card-body">
<h2 class="card-title flex justify-between items-center">
<div class="flex items-center gap-2">
<Zap class="w-5 h-5" />
调试器波形捕获
</div>
<div class="flex items-center gap-2">
<button
class="btn btn-sm btn-error"
@click="handleDeleteData"
:disabled="!captureData"
>
清空
</button>
</div>
</h2>
<WaveformDisplay :data="captureData">
<button
class="group relative px-8 py-3 bg-gradient-to-r text-white font-medium rounded-lg shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-200 ease-in-out focus:outline-none focus:ring-4 active:scale-95"
:class="{
'from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 focus:ring-blue-300':
!isCapturing,
'from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 focus:ring-red-300':
isCapturing,
}"
@click="isCapturing ? stopCapture() : startCapture()"
>
<span class="flex items-center gap-2">
<template v-if="isCapturing">
<Square class="w-5 h-5" />
停止捕获
</template>
<template v-else>
<Play class="w-5 h-5" />
开始捕获
</template>
</span>
</button>
</WaveformDisplay>
</div>
</div>
<!-- Debugger 通道配置 -->
<div class="card m-5 bg-base-200 shadow-2xl">
<div class="card-body">
<h2 class="card-title mb-4">调试器通道配置</h2>
<div class="overflow-x-auto flex flex-col gap-10">
<!-- 通道状态概览 -->
<div
class="stats stats-horizontal bg-base-100 shadow flex justify-between"
>
<div class="stat">
<div class="stat-title">总通道数</div>
<div class="stat-value text-primary">16</div>
<div class="stat-desc">逻辑分析仪通道</div>
</div>
<div class="stat">
<div class="stat-title">启用通道</div>
<div class="stat-value text-success">
{{ channels.filter((ch) => ch.visible).length }}
</div>
<div class="stat-desc">当前激活通道</div>
</div>
<div class="stat">
<div class="stat-title">采样率</div>
<div class="stat-value text-info">5MHz</div>
<div class="stat-desc">最大采样频率</div>
</div>
</div>
<!-- 表格 -->
<div class="space-y-2">
<!-- 表头 -->
<div
class="grid grid-cols-6 justify-items-center gap-2 p-2 bg-base-300 rounded-lg text-sm font-medium"
>
<span>名称</span>
<span>显示</span>
<span>颜色</span>
<span>触发模式</span>
<span>数据位数</span>
<span>操作</span>
</div>
<!-- 通道列表 -->
<div
v-for="(ch, idx) in channels"
:key="idx"
class="grid grid-cols-6 justify-items-center gap-2 p-2 bg-base-200 rounded-lg hover:bg-base-300 transition-colors"
>
<input
v-model="ch.name"
class="input input-bordered w-full"
:placeholder="`通道${idx + 1}`"
/>
<input
type="checkbox"
v-model="ch.visible"
class="toggle toggle-primary"
/>
<input
type="color"
v-model="ch.color"
class="w-8 h-8 rounded border-2 border-base-300 cursor-pointer"
/>
<select
v-model="ch.trigger"
class="select select-bordered w-full"
>
<option
v-for="mode in triggerModes"
:key="mode.value"
:value="mode.value"
>
{{ mode.label }}
</option>
</select>
<input
type="number"
min="1"
max="32"
v-model.number="ch.width"
class="input input-bordered w-full"
/>
<button class="btn btn-error" @click="removeChannel(idx)">
删除
</button>
</div>
<!-- 添加通道按钮 -->
<div class="flex justify-center mt-2">
<button class="btn btn-primary w-100" @click="addChannel">
添加通道
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import WaveformDisplay from '@/components/WaveformDisplay/WaveformDisplay.vue';
</script>
<script setup lang="ts">
import { CaptureMode } from "@/APIClient";
import { useAlertStore } from "@/components/Alert";
import type { LogicDataType } from "@/components/WaveformDisplay";
import WaveformDisplay from "@/components/WaveformDisplay/WaveformDisplay.vue";
import { AuthManager } from "@/utils/AuthManager";
import { useRequiredInjection } from "@/utils/Common";
import { useLocalStorage } from "@vueuse/core";
import { Play, Square, Zap } from "lucide-vue-next";
import { ref } from "vue";
interface DebugChannel {
name: string;
visible: boolean;
color: string;
trigger: CaptureMode;
width: number;
}
const triggerModes = [
{ value: CaptureMode.None, label: "x (无关)" },
{ value: CaptureMode.Logic0, label: "0 (低电平)" },
{ value: CaptureMode.Logic1, label: "1 (高电平)" },
{ value: CaptureMode.Rise, label: "↑ (上升沿)" },
{ value: CaptureMode.Fall, label: "↓ (下降沿)" },
];
const channels = useLocalStorage<DebugChannel[]>("debugger-channels", []);
const captureData = ref<LogicDataType>();
const alert = useRequiredInjection(useAlertStore);
const isCapturing = ref(false);
async function startCapture() {
if (channels.value.length === 0) {
alert.error("请至少添加一个通道");
return;
}
isCapturing.value = true;
const client = AuthManager.createAuthenticatedDebuggerClient();
for (let i = 0; i < channels.value.length; i++) {
const channel = channels.value[i];
if (!channel.visible) continue;
if (!channel.name) {
alert.error(`通道 ${i + 1} 名称不能为空`);
isCapturing.value = false;
return;
}
if (channel.width < 1 || channel.width > 32) {
alert.error(`通道 ${i + 1} 数据位数必须在1到32之间`);
isCapturing.value = false;
return;
}
try {
let ret = await client.setMode(i, channel.trigger);
if (!ret) {
alert.error(`设置通道 ${i + 1} 触发模式失败`);
isCapturing.value = false;
return;
}
} catch (error: any) {
alert.error(`设置通道 ${i + 1} 触发模式失败: ${error.message}`);
isCapturing.value = false;
return;
}
}
try {
let ret = await client.startTrigger();
if (!ret) {
alert.error("开始捕获失败,请检查连接");
isCapturing.value = false;
return;
}
while ((await client.readFlag()) !== 1 && isCapturing.value) {
await new Promise((resolve) => setTimeout(resolve, 500));
}
if (!isCapturing.value) {
alert.info("捕获已停止");
return;
}
const base64Data = await client.readData(0);
const binaryString = atob(base64Data);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
//
// 1.
const enabledChannels = channels.value.filter((ch) => ch.visible);
const widths = enabledChannels.map((ch) => ch.width);
const totalBitsPerSample = widths.reduce((a, b) => a + b, 0);
// 2.
const totalBits = bytes.length * 8;
const sampleCount = Math.floor(totalBits / totalBitsPerSample);
// 3.
let bitOffset = 0;
const channelValues: number[][] = enabledChannels.map(() => []);
for (let sampleIdx = 0; sampleIdx < sampleCount; sampleIdx++) {
for (let chIdx = 0; chIdx < enabledChannels.length; chIdx++) {
const width = widths[chIdx];
let value = 0;
for (let w = 0; w < width; w++) {
const absBit = bitOffset + w;
const byteIdx = Math.floor(absBit / 8);
const bitInByte = absBit % 8;
const bit = (bytes[byteIdx] >> (7 - bitInByte)) & 1;
value = (value << 1) | bit;
}
channelValues[chIdx].push(value);
bitOffset += width;
}
}
// 4. LogicDataType
const x: number[] = [];
const xUnit = "us"; // 5MHz -> 0.2us/point, WaveformDisplayns/ms/us/sus
for (let i = 0; i < sampleCount; i++) {
x.push(i * 0.2); // 0.2us per sample
}
const y = enabledChannels.map((ch, idx) => ({
enabled: true,
type: ch.width === 1 ? ("logic" as const) : ("number" as const),
name: ch.name,
color: ch.color,
value: channelValues[idx],
base: ch.width === 1 ? ("bin" as const) : ("hex" as const),
}));
captureData.value = {
x: x,
y: y,
xUnit: "us",
};
} catch (error: any) {
alert.error(`开始捕获失败: ${error.message}`);
isCapturing.value = false;
return;
}
}
function stopCapture() {
isCapturing.value = false;
}
function handleDeleteData() {
captureData.value = undefined;
}
function addChannel() {
if (channels.value.length >= 16) {
alert.error("最多只能添加16个通道");
return;
}
channels.value.push({
name: `CH${channels.value.length + 1}`,
visible: true,
color: "#00bcd4",
trigger: CaptureMode.None,
width: 1,
});
}
function removeChannel(idx: number) {
channels.value.splice(idx, 1);
}
</script>