feat: 修改后端apiclient生成逻辑
fix: 修复debugger获取flag失败的问题 refactor: 重新编写debugger前后端逻辑
This commit is contained in:
@@ -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)
|
||||
|
@@ -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" />
|
||||
|
@@ -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")]
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user