feat: 修改后端apiclient生成逻辑

fix: 修复debugger获取flag失败的问题
refactor: 重新编写debugger前后端逻辑
This commit is contained in:
2025-07-30 15:31:11 +08:00
parent 6dfd275091
commit 3257a68407
11 changed files with 4194 additions and 1733 deletions

View File

@@ -5,8 +5,11 @@ using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.FileProviders;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using NJsonSchema.CodeGeneration.TypeScript;
using NLog;
using NLog.Web;
using NSwag;
using NSwag.CodeGeneration.TypeScript;
using NSwag.Generation.Processors.Security;
using server.Services;
@@ -191,6 +194,34 @@ try
// Setup Program
MsgBus.Init();
// Generate API Client
app.MapGet("GetAPIClientCode", async (HttpContext context) =>
{
try
{
var document = await OpenApiDocument.FromUrlAsync($"http://{Global.localhost}:5000/swagger/v1/swagger.json");
var settings = new TypeScriptClientGeneratorSettings
{
ClassName = "{controller}Client",
UseAbortSignal = false,
Template = TypeScriptTemplate.Axios,
TypeScriptGeneratorSettings = {
},
};
var generator = new TypeScriptClientGenerator(document, settings);
var code = generator.GenerateFile();
return Results.Text(code, "text/plain; charset=utf-8", Encoding.UTF8);
}
catch (Exception err)
{
logger.Error(err);
return Results.Problem(err.ToString());
}
});
app.Run();
}
catch (Exception exception)

View File

@@ -26,6 +26,7 @@
<PackageReference Include="NLog" Version="5.4.0" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.4.0" />
<PackageReference Include="NSwag.AspNetCore" Version="14.3.0" />
<PackageReference Include="NSwag.CodeGeneration.TypeScript" Version="14.4.0" />
<PackageReference Include="OpenCvSharp4" Version="4.11.0.20250507" />
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.11.0.20250507" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />

View File

@@ -6,7 +6,7 @@ using Peripherals.DebuggerClient;
namespace server.Controllers;
/// <summary>
/// FPGA调试器控制器
/// FPGA调试器控制器提供信号捕获、触发、数据读取等调试相关API
/// </summary>
[ApiController]
[Route("api/[controller]")]
@@ -15,8 +15,81 @@ public class DebuggerController : ControllerBase
{
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
/// <summary>
/// 表示单个信号通道的配置信息
/// </summary>
public class ChannelConfig
{
/// <summary>
/// 通道名称
/// </summary>
required public string name;
/// <summary>
/// 通道显示颜色(如前端波形显示用)
/// </summary>
required public string color;
/// <summary>
/// 通道信号线宽度(位数)
/// </summary>
required public UInt32 wireWidth;
/// <summary>
/// 信号线在父端口中的起始索引bit
/// </summary>
required public UInt32 wireStartIndex;
/// <summary>
/// 父端口编号
/// </summary>
required public UInt32 parentPort;
/// <summary>
/// 捕获模式(如上升沿、下降沿等)
/// </summary>
required public CaptureMode mode;
}
/// <summary>
/// 调试器整体配置信息
/// </summary>
public class DebuggerConfig
{
/// <summary>
/// 时钟频率
/// </summary>
required public UInt32 clkFreq;
/// <summary>
/// 总端口数量
/// </summary>
required public UInt32 totalPortNum;
/// <summary>
/// 捕获深度(采样点数)
/// </summary>
required public UInt32 captureDepth;
/// <summary>
/// 触发器数量
/// </summary>
required public UInt32 triggerNum;
/// <summary>
/// 所有信号通道的配置信息
/// </summary>
required public ChannelConfig[] channelConfigs;
}
/// <summary>
/// 单个通道的捕获数据
/// </summary>
public class ChannelCaptureData
{
/// <summary>
/// 通道名称
/// </summary>
required public string name;
/// <summary>
/// 通道捕获到的数据Base64编码的UInt32数组
/// </summary>
required public string data;
}
/// <summary>
/// 获取调试器实例
/// 获取当前用户绑定的调试器实例
/// </summary>
private DebuggerClient? GetDebugger()
{
@@ -50,19 +123,21 @@ public class DebuggerController : ControllerBase
}
/// <summary>
/// 设置捕获模式
/// 设置指定信号线的捕获模式
/// </summary>
/// <param name="wireNum">信号线编号0~511</param>
/// <param name="mode">捕获模式</param>
[HttpPost("SetMode")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<IActionResult> SetMode(int channelNum, CaptureMode mode)
public async Task<IActionResult> SetMode(UInt32 wireNum, CaptureMode mode)
{
if (channelNum > 0x0F)
if (wireNum > 512)
{
return BadRequest($"最多只能建立16个通道");
return BadRequest($"最多只能建立512位信号线");
}
try
@@ -71,7 +146,7 @@ public class DebuggerController : ControllerBase
if (debugger == null)
return BadRequest("用户未绑定有效的实验板");
var result = await debugger.SetMode((byte)channelNum, mode);
var result = await debugger.SetMode(wireNum, mode);
if (!result.IsSuccessful)
{
logger.Error($"设置捕获模式失败: {result.Error}");
@@ -88,7 +163,58 @@ public class DebuggerController : ControllerBase
}
/// <summary>
/// 启动触发器
/// 为每个通道中的每根线设置捕获模式
/// </summary>
/// <param name="config">调试器配置信息,包含所有通道的捕获模式设置</param>
[HttpPost("SetChannelsMode")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<IActionResult> SetChannelsMode([FromBody] DebuggerConfig config)
{
if (config == null || config.channelConfigs == null)
return BadRequest("配置无效");
try
{
var debugger = GetDebugger();
if (debugger == null)
return BadRequest("用户未绑定有效的实验板");
foreach (var channel in config.channelConfigs)
{
// 检查每个通道的配置
if (channel.wireWidth > 32 ||
channel.wireStartIndex > 32 ||
channel.wireStartIndex + channel.wireWidth > 32)
{
return BadRequest($"通道 {channel.name} 配置错误");
}
for (uint i = 0; i < channel.wireWidth; i++)
{
var result = await debugger.SetMode(channel.wireStartIndex * (channel.parentPort * 32) + i, channel.mode);
if (!result.IsSuccessful)
{
logger.Error($"设置通道 {channel.name} 第 {i} 根线捕获模式失败: {result.Error}");
return StatusCode(StatusCodes.Status500InternalServerError, $"设置通道 {channel.name} 第 {i} 根线捕获模式失败");
}
}
}
return Ok(true);
}
catch (Exception ex)
{
logger.Error(ex, "为每个通道中的每根线设置捕获模式时发生异常");
return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试");
}
}
/// <summary>
/// 启动触发器,开始信号捕获
/// </summary>
[HttpPost("StartTrigger")]
[EnableCors("Users")]
@@ -120,6 +246,46 @@ public class DebuggerController : ControllerBase
}
}
/// <summary>
/// 重新开始触发(刷新后再启动触发器)
/// </summary>
[HttpPost("RestartTrigger")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<IActionResult> RestartTrigger()
{
try
{
var debugger = GetDebugger();
if (debugger == null)
return BadRequest("用户未绑定有效的实验板");
var refreshResult = await debugger.Refresh();
if (!refreshResult.IsSuccessful)
{
logger.Error($"刷新调试器状态失败: {refreshResult.Error}");
return StatusCode(StatusCodes.Status500InternalServerError, "刷新调试器状态失败");
}
var startResult = await debugger.StartTrigger();
if (!startResult.IsSuccessful)
{
logger.Error($"启动触发器失败: {startResult.Error}");
return StatusCode(StatusCodes.Status500InternalServerError, "启动触发器失败");
}
return Ok(startResult.Value);
}
catch (Exception ex)
{
logger.Error(ex, "重新开始触发时发生异常");
return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试");
}
}
/// <summary>
/// 读取触发器状态标志
/// </summary>
@@ -187,32 +353,105 @@ public class DebuggerController : ControllerBase
}
/// <summary>
/// 读取捕获数据
/// 读取捕获数据(等待触发完成后返回各通道采样数据)
/// </summary>
/// <param name="config">调试器配置信息,包含采样深度、端口数、通道配置等</param>
/// <param name="cancellationToken">取消操作的令牌</param>
[HttpGet("ReadData")]
[EnableCors("Users")]
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ChannelCaptureData[]), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<IActionResult> ReadData([FromQuery] ushort offset = 0)
public async Task<IActionResult> ReadData([FromBody] DebuggerConfig config, CancellationToken cancellationToken)
{
// 检查每个通道的配置
foreach (var channel in config.channelConfigs)
{
if (channel.wireWidth > 32 ||
channel.wireStartIndex > 32 ||
channel.wireStartIndex + channel.wireWidth > 32)
{
return BadRequest($"通道 {channel.name} 配置错误");
}
}
try
{
var debugger = GetDebugger();
if (debugger == null)
return BadRequest("用户未绑定有效的实验板");
var result = await debugger.ReadData(offset);
if (!result.IsSuccessful)
// 等待捕获标志位
while (true)
{
logger.Error($"读取捕获数据失败: {result.Error}");
cancellationToken.ThrowIfCancellationRequested();
var flagResult = await debugger.ReadFlag();
if (!flagResult.IsSuccessful)
{
logger.Error($"读取捕获标志失败: {flagResult.Error}");
return StatusCode(StatusCodes.Status500InternalServerError, "读取捕获标志失败");
}
if (flagResult.Value == 1)
{
var clearResult = await debugger.ClearFlag();
if (!clearResult.IsSuccessful)
{
logger.Error($"清除捕获标志失败: {clearResult.Error}");
return StatusCode(StatusCodes.Status500InternalServerError, "清除捕获标志失败");
}
break;
}
await Task.Delay(500, cancellationToken);
}
var dataResult = await debugger.ReadData(config.totalPortNum);
if (!dataResult.IsSuccessful)
{
logger.Error($"读取捕获数据失败: {dataResult.Error}");
return StatusCode(StatusCodes.Status500InternalServerError, "读取捕获数据失败");
}
// 返回Base64编码
var base64Data = Convert.ToBase64String(result.Value);
return Ok(base64Data);
var rawData = dataResult.Value;
int depth = (int)config.captureDepth;
int portDataLen = 4 * depth;
int portNum = (int)config.totalPortNum;
var channelDataList = new List<ChannelCaptureData>();
foreach (var channel in config.channelConfigs)
{
int port = (int)channel.parentPort;
int wireStart = (int)channel.wireStartIndex;
int wireWidth = (int)channel.wireWidth;
// 每个port的数据长度
int portOffset = port * portDataLen;
var channelUintArr = new UInt32[depth];
for (int i = 0; i < depth; i++)
{
// 取出该port的第i个采样点的4字节
int sampleOffset = portOffset + i * 4;
if (sampleOffset + 4 > rawData.Length)
{
logger.Error($"数据越界: port {port}, sample {i}");
return StatusCode(StatusCodes.Status500InternalServerError, "数据越界");
}
UInt32 sample = BitConverter.ToUInt32(rawData, sampleOffset);
// 提取wireWidth位
UInt32 mask = (wireWidth == 32) ? 0xFFFFFFFF : ((1u << wireWidth) - 1u);
channelUintArr[i] = (sample >> wireStart) & mask;
}
var base64 = Convert.ToBase64String(channelUintArr.SelectMany(BitConverter.GetBytes).ToArray());
channelDataList.Add(new ChannelCaptureData { name = channel.name, data = base64 });
}
return Ok(channelDataList.ToArray());
}
catch (OperationCanceledException)
{
logger.Info("读取捕获数据请求被取消");
return StatusCode(StatusCodes.Status499ClientClosedRequest, "客户端已取消请求");
}
catch (Exception ex)
{
@@ -222,7 +461,7 @@ public class DebuggerController : ControllerBase
}
/// <summary>
/// 刷新调试器状态
/// 刷新调试器状态(重置采集状态等)
/// </summary>
[HttpPost("Refresh")]
[EnableCors("Users")]

View File

@@ -119,18 +119,18 @@ public class DebuggerClient
/// <summary>
/// 设置信号捕获模式
/// </summary>
/// <param name="channelNum">要设置的通道</param>
/// <param name="wireNum">要设置的线</param>
/// <param name="mode">要设置的捕获模式</param>
/// <returns>操作结果成功返回true失败返回错误信息</returns>
public async ValueTask<Result<bool>> SetMode(byte channelNum, CaptureMode mode)
public async ValueTask<Result<bool>> SetMode(UInt32 wireNum, CaptureMode mode)
{
if (channelNum > 0x0F)
if (wireNum > 512)
{
return new(new ArgumentException($"Channel Num can't be over 16, but receive num: {channelNum}"));
return new(new ArgumentException($"Wire Num can't be over 512, but receive num: {wireNum}"));
}
UInt32 data = ((UInt32)mode);
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, DebuggerAddr.Mode + channelNum, data, this.timeout);
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, DebuggerAddr.Mode + wireNum, data, this.timeout);
if (!ret.IsSuccessful)
{
logger.Error($"Failed to set mode: {ret.Error}");
@@ -181,7 +181,7 @@ public class DebuggerClient
logger.Error("ReadAddr returned invalid data for flag");
return new(new Exception("Failed to read flag"));
}
return ret.Value.Options.Data[0];
return ret.Value.Options.Data[3];
}
/// <summary>
@@ -207,30 +207,26 @@ public class DebuggerClient
/// <summary>
/// 从指定偏移地址读取捕获的数据
/// </summary>
/// <param name="offset">数据读取的偏移地址</param>
/// <returns>操作结果,成功返回32KB的捕获数据,失败返回错误信息</returns>
public async ValueTask<Result<byte[]>> ReadData(UInt16 offset)
/// <param name="portNum">Port数量</param>
/// <returns>操作结果,成功返回捕获数据,失败返回错误信息</returns>
public async ValueTask<Result<byte[]>> ReadData(UInt32 portNum)
{
var captureData = new byte[1024 * 32];
var captureData = new byte[1024 * 4 * portNum];
{
var ret = await UDPClientPool.ReadAddr4BytesAsync(this.ep, this.taskID, this.captureDataAddr + offset, 512, this.timeout);
var ret = await UDPClientPool.ReadAddr4BytesAsync(this.ep, this.taskID, this.captureDataAddr, captureData.Length, this.timeout);
if (!ret.IsSuccessful)
{
logger.Error($"Failed to read data: {ret.Error}");
return new(ret.Error);
}
Buffer.BlockCopy(ret.Value, 0, captureData, 0, 512 * 4);
}
{
var ret = await UDPClientPool.ReadAddr4BytesAsync(this.ep, this.taskID, this.captureDataAddr + offset + 512, 512, this.timeout);
if (!ret.IsSuccessful)
if (ret.Value.Length != captureData.Length)
{
logger.Error($"Failed to read data: {ret.Error}");
return new(ret.Error);
logger.Error($"Receive capture data length should be {captureData.Length} instead of {ret.Value.Length}");
return new(new Exception($"Receive capture data length should be {captureData.Length} instead of {ret.Value.Length}"));
}
Buffer.BlockCopy(ret.Value, 0, captureData, 512 * 4, 512 * 4);
Buffer.BlockCopy(ret.Value, 0, captureData, 0, captureData.Length);
}
return captureData;