469 lines
17 KiB
C#
469 lines
17 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
||
using Microsoft.AspNetCore.Cors;
|
||
using Microsoft.AspNetCore.Mvc;
|
||
using Peripherals.DebuggerClient;
|
||
|
||
namespace server.Controllers;
|
||
|
||
/// <summary>
|
||
/// FPGA调试器控制器,提供信号捕获、触发、数据读取等调试相关API
|
||
/// </summary>
|
||
[ApiController]
|
||
[Route("api/[controller]")]
|
||
[Authorize]
|
||
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()
|
||
{
|
||
try
|
||
{
|
||
var userName = User.Identity?.Name;
|
||
if (string.IsNullOrEmpty(userName))
|
||
return null;
|
||
|
||
using var db = new Database.AppDataConnection();
|
||
var userRet = db.GetUserByName(userName);
|
||
if (!userRet.IsSuccessful || !userRet.Value.HasValue)
|
||
return null;
|
||
|
||
var user = userRet.Value.Value;
|
||
if (user.BoardID == Guid.Empty)
|
||
return null;
|
||
|
||
var boardRet = db.GetBoardByID(user.BoardID);
|
||
if (!boardRet.IsSuccessful || !boardRet.Value.HasValue)
|
||
return null;
|
||
|
||
var board = boardRet.Value.Value;
|
||
return new DebuggerClient(board.IpAddr, board.Port, 1);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, "获取调试器实例时发生异常");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <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(UInt32 wireNum, CaptureMode mode)
|
||
{
|
||
if (wireNum > 512)
|
||
{
|
||
return BadRequest($"最多只能建立512位信号线");
|
||
}
|
||
|
||
try
|
||
{
|
||
var debugger = GetDebugger();
|
||
if (debugger == null)
|
||
return BadRequest("用户未绑定有效的实验板");
|
||
|
||
var result = await debugger.SetMode(wireNum, mode);
|
||
if (!result.IsSuccessful)
|
||
{
|
||
logger.Error($"设置捕获模式失败: {result.Error}");
|
||
return StatusCode(StatusCodes.Status500InternalServerError, "设置捕获模式失败");
|
||
}
|
||
|
||
return Ok(result.Value);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, "设置捕获模式时发生异常");
|
||
return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试");
|
||
}
|
||
}
|
||
|
||
/// <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")]
|
||
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||
public async Task<IActionResult> StartTrigger()
|
||
{
|
||
try
|
||
{
|
||
var debugger = GetDebugger();
|
||
if (debugger == null)
|
||
return BadRequest("用户未绑定有效的实验板");
|
||
|
||
var result = await debugger.StartTrigger();
|
||
if (!result.IsSuccessful)
|
||
{
|
||
logger.Error($"启动触发器失败: {result.Error}");
|
||
return StatusCode(StatusCodes.Status500InternalServerError, "启动触发器失败");
|
||
}
|
||
|
||
return Ok(result.Value);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, "启动触发器时发生异常");
|
||
return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取触发器状态标志
|
||
/// </summary>
|
||
[HttpGet("ReadFlag")]
|
||
[EnableCors("Users")]
|
||
[ProducesResponseType(typeof(byte), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||
public async Task<IActionResult> ReadFlag()
|
||
{
|
||
try
|
||
{
|
||
var debugger = GetDebugger();
|
||
if (debugger == null)
|
||
return BadRequest("用户未绑定有效的实验板");
|
||
|
||
var result = await debugger.ReadFlag();
|
||
if (!result.IsSuccessful)
|
||
{
|
||
logger.Error($"读取触发器状态标志失败: {result.Error}");
|
||
return StatusCode(StatusCodes.Status500InternalServerError, "读取触发器状态标志失败");
|
||
}
|
||
|
||
return Ok(result.Value);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, "读取触发器状态标志时发生异常");
|
||
return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除触发器状态标志
|
||
/// </summary>
|
||
[HttpPost("ClearFlag")]
|
||
[EnableCors("Users")]
|
||
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||
public async Task<IActionResult> ClearFlag()
|
||
{
|
||
try
|
||
{
|
||
var debugger = GetDebugger();
|
||
if (debugger == null)
|
||
return BadRequest("用户未绑定有效的实验板");
|
||
|
||
var result = await debugger.ClearFlag();
|
||
if (!result.IsSuccessful)
|
||
{
|
||
logger.Error($"清除触发器状态标志失败: {result.Error}");
|
||
return StatusCode(StatusCodes.Status500InternalServerError, "清除触发器状态标志失败");
|
||
}
|
||
|
||
return Ok(result.Value);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, "清除触发器状态标志时发生异常");
|
||
return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取捕获数据(等待触发完成后返回各通道采样数据)
|
||
/// </summary>
|
||
/// <param name="config">调试器配置信息,包含采样深度、端口数、通道配置等</param>
|
||
/// <param name="cancellationToken">取消操作的令牌</param>
|
||
[HttpPost("ReadData")]
|
||
[EnableCors("Users")]
|
||
[ProducesResponseType(typeof(ChannelCaptureData[]), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||
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("用户未绑定有效的实验板");
|
||
|
||
// 等待捕获标志位
|
||
while (true)
|
||
{
|
||
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, "读取捕获数据失败");
|
||
}
|
||
|
||
var freshResult = await debugger.Refresh();
|
||
if (!freshResult.IsSuccessful)
|
||
{
|
||
logger.Error($"刷新调试器状态失败: {freshResult.Error}");
|
||
return StatusCode(StatusCodes.Status500InternalServerError, "刷新调试器状态失败");
|
||
}
|
||
|
||
var rawData = dataResult.Value;
|
||
logger.Debug($"rawData: {BitConverter.ToString(rawData)}");
|
||
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, "数据越界");
|
||
}
|
||
var sampleBytes = rawData[sampleOffset..(sampleOffset + 4)];
|
||
UInt32 sample = Common.Number.BytesToUInt32(sampleBytes, true).Value;
|
||
// 提取wireWidth位
|
||
UInt32 mask = (wireWidth == 32) ? 0xFFFFFFFF : ((1u << wireWidth) - 1u);
|
||
channelUintArr[i] = (sample >> wireStart) & mask;
|
||
}
|
||
var channelBytes = new byte[4 * depth];
|
||
Buffer.BlockCopy(channelUintArr, 0, channelBytes, 0, channelBytes.Length);
|
||
channelBytes = Common.Number.ReverseBytes(channelBytes, 4).Value;
|
||
logger.Debug($"{channel.name} HexData: {BitConverter.ToString(channelBytes)}");
|
||
var base64 = Convert.ToBase64String(channelBytes);
|
||
channelDataList.Add(new ChannelCaptureData { name = channel.name, data = base64 });
|
||
}
|
||
|
||
return Ok(channelDataList.ToArray());
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
logger.Info("读取捕获数据请求被取消");
|
||
return StatusCode(StatusCodes.Status499ClientClosedRequest, "客户端已取消请求");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, "读取捕获数据时发生异常");
|
||
return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 刷新调试器状态(重置采集状态等)
|
||
/// </summary>
|
||
[HttpPost("Refresh")]
|
||
[EnableCors("Users")]
|
||
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||
public async Task<IActionResult> Refresh()
|
||
{
|
||
try
|
||
{
|
||
var debugger = GetDebugger();
|
||
if (debugger == null)
|
||
return BadRequest("用户未绑定有效的实验板");
|
||
|
||
var result = await debugger.Refresh();
|
||
if (!result.IsSuccessful)
|
||
{
|
||
logger.Error($"刷新调试器状态失败: {result.Error}");
|
||
return StatusCode(StatusCodes.Status500InternalServerError, "刷新调试器状态失败");
|
||
}
|
||
|
||
return Ok(result.Value);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error(ex, "刷新调试器状态时发生异常");
|
||
return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试");
|
||
}
|
||
}
|
||
}
|