Compare commits
No commits in common. "55edfd771ec9a2323326fdb66d3579daa8405f9c" and "b6720d867d134a27f618749998dfb4564a0aed1b" have entirely different histories.
55edfd771e
...
b6720d867d
|
@ -180,7 +180,8 @@ try
|
||||||
builder.Services.AddHostedService(provider => provider.GetRequiredService<HttpHdmiVideoStreamService>());
|
builder.Services.AddHostedService(provider => provider.GetRequiredService<HttpHdmiVideoStreamService>());
|
||||||
|
|
||||||
// 添加进度跟踪服务
|
// 添加进度跟踪服务
|
||||||
builder.Services.AddSingleton<ProgressTracker>();
|
builder.Services.AddSingleton<ProgressTrackerService>();
|
||||||
|
builder.Services.AddHostedService(provider => provider.GetRequiredService<ProgressTrackerService>());
|
||||||
|
|
||||||
// Application Settings
|
// Application Settings
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
@ -257,8 +258,6 @@ try
|
||||||
|
|
||||||
// Setup Program
|
// Setup Program
|
||||||
MsgBus.Init();
|
MsgBus.Init();
|
||||||
var progressTracker = app.Services.GetRequiredService<ProgressTracker>();
|
|
||||||
MsgBus.SetProgressTracker(progressTracker);
|
|
||||||
|
|
||||||
// Generate API Client
|
// Generate API Client
|
||||||
app.MapGet("GetAPIClientCode", async (HttpContext context) =>
|
app.MapGet("GetAPIClientCode", async (HttpContext context) =>
|
||||||
|
|
|
@ -16,12 +16,17 @@ public class JtagController : ControllerBase
|
||||||
{
|
{
|
||||||
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
private readonly ProgressTracker _tracker = MsgBus.ProgressTracker;
|
private readonly ProgressTrackerService _tracker;
|
||||||
private readonly UserManager _userManager = new();
|
private readonly UserManager _userManager = new();
|
||||||
private readonly ResourceManager _resourceManager = new();
|
private readonly ResourceManager _resourceManager = new();
|
||||||
|
|
||||||
private const string BITSTREAM_PATH = "bitstream/Jtag";
|
private const string BITSTREAM_PATH = "bitstream/Jtag";
|
||||||
|
|
||||||
|
public JtagController(ProgressTrackerService tracker)
|
||||||
|
{
|
||||||
|
_tracker = tracker;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 控制器首页信息
|
/// 控制器首页信息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -183,8 +188,8 @@ public class JtagController : ControllerBase
|
||||||
logger.Info($"User {username} processing bitstream file of size: {fileBytes.Length} bytes");
|
logger.Info($"User {username} processing bitstream file of size: {fileBytes.Length} bytes");
|
||||||
|
|
||||||
// 定义进度跟踪
|
// 定义进度跟踪
|
||||||
var taskId = _tracker.CreateTask(1000);
|
var (taskId, progress) = _tracker.CreateTask(cancelToken);
|
||||||
_tracker.AdvanceProgress(taskId, 10);
|
progress.Report(10);
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
|
@ -205,8 +210,7 @@ public class JtagController : ControllerBase
|
||||||
if (!retBuffer.IsSuccessful)
|
if (!retBuffer.IsSuccessful)
|
||||||
{
|
{
|
||||||
logger.Error($"User {username} failed to reverse bytes: {retBuffer.Error}");
|
logger.Error($"User {username} failed to reverse bytes: {retBuffer.Error}");
|
||||||
_tracker.FailProgress(taskId,
|
progress.Error($"User {username} failed to reverse bytes: {retBuffer.Error}");
|
||||||
$"User {username} failed to reverse bytes: {retBuffer.Error}");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
revBuffer = retBuffer.Value;
|
revBuffer = retBuffer.Value;
|
||||||
|
@ -224,7 +228,7 @@ public class JtagController : ControllerBase
|
||||||
var processedBytes = outputStream.ToArray();
|
var processedBytes = outputStream.ToArray();
|
||||||
logger.Info($"User {username} processed {totalBytesProcessed} bytes for device {address}");
|
logger.Info($"User {username} processed {totalBytesProcessed} bytes for device {address}");
|
||||||
|
|
||||||
_tracker.AdvanceProgress(taskId, 20);
|
progress.Report(20);
|
||||||
|
|
||||||
// 下载比特流
|
// 下载比特流
|
||||||
var jtagCtrl = new Peripherals.JtagClient.Jtag(address, port);
|
var jtagCtrl = new Peripherals.JtagClient.Jtag(address, port);
|
||||||
|
@ -233,13 +237,12 @@ public class JtagController : ControllerBase
|
||||||
if (ret.IsSuccessful)
|
if (ret.IsSuccessful)
|
||||||
{
|
{
|
||||||
logger.Info($"User {username} successfully downloaded bitstream '{resource.ResourceName}' to device {address}");
|
logger.Info($"User {username} successfully downloaded bitstream '{resource.ResourceName}' to device {address}");
|
||||||
_tracker.CompleteProgress(taskId);
|
progress.Finish();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logger.Error($"User {username} failed to download bitstream to device {address}: {ret.Error}");
|
logger.Error($"User {username} failed to download bitstream to device {address}: {ret.Error}");
|
||||||
_tracker.FailProgress(taskId,
|
progress.Error($"User {username} failed to download bitstream to device {address}: {ret.Error}");
|
||||||
$"User {username} failed to download bitstream to device {address}: {ret.Error}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -114,7 +114,7 @@ public class SwitchController : ControllerBase
|
||||||
|
|
||||||
for (int i = 0; i < keyStatus.Length; i++)
|
for (int i = 0; i < keyStatus.Length; i++)
|
||||||
{
|
{
|
||||||
var result = await switchCtrl.SetSwitchOnOff(i + 1, keyStatus[i]);
|
var result = await switchCtrl.SetSwitchOnOff(i, keyStatus[i]);
|
||||||
if (!result.IsSuccessful)
|
if (!result.IsSuccessful)
|
||||||
{
|
{
|
||||||
logger.Error(result.Error, $"SetSwitchOnOff({i}, {keyStatus[i]}) failed");
|
logger.Error(result.Error, $"SetSwitchOnOff({i}, {keyStatus[i]}) failed");
|
||||||
|
|
|
@ -1,20 +1,17 @@
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using System.Security.Claims;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
using TypedSignalR.Client;
|
using TypedSignalR.Client;
|
||||||
using Tapper;
|
using Tapper;
|
||||||
using server.Services;
|
using server.Services;
|
||||||
|
|
||||||
#pragma warning disable 1998
|
|
||||||
|
|
||||||
namespace server.Hubs;
|
namespace server.Hubs;
|
||||||
|
|
||||||
[Hub]
|
[Hub]
|
||||||
public interface IProgressHub
|
public interface IProgressHub
|
||||||
{
|
{
|
||||||
Task<bool> Join(string taskId);
|
Task<bool> Join(string taskId);
|
||||||
Task<bool> Leave(string taskId);
|
|
||||||
Task<ProgressInfo?> GetProgress(string taskId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Receiver]
|
[Receiver]
|
||||||
|
@ -26,7 +23,8 @@ public interface IProgressReceiver
|
||||||
[TranspilationSource]
|
[TranspilationSource]
|
||||||
public enum ProgressStatus
|
public enum ProgressStatus
|
||||||
{
|
{
|
||||||
Running,
|
Pending,
|
||||||
|
InProgress,
|
||||||
Completed,
|
Completed,
|
||||||
Canceled,
|
Canceled,
|
||||||
Failed
|
Failed
|
||||||
|
@ -35,10 +33,10 @@ public enum ProgressStatus
|
||||||
[TranspilationSource]
|
[TranspilationSource]
|
||||||
public class ProgressInfo
|
public class ProgressInfo
|
||||||
{
|
{
|
||||||
public required string TaskId { get; set; }
|
public virtual string TaskId { get; } = string.Empty;
|
||||||
public required ProgressStatus Status { get; set; }
|
public virtual ProgressStatus Status { get; }
|
||||||
public required int ProgressPercent { get; set; }
|
public virtual int ProgressPercent { get; } = 0;
|
||||||
public required string ErrorMessage { get; set; }
|
public virtual string ErrorMessage { get; } = string.Empty;
|
||||||
};
|
};
|
||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
|
@ -46,32 +44,18 @@ public class ProgressInfo
|
||||||
public class ProgressHub : Hub<IProgressReceiver>, IProgressHub
|
public class ProgressHub : Hub<IProgressReceiver>, IProgressHub
|
||||||
{
|
{
|
||||||
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||||||
private readonly ProgressTracker _progressTracker = MsgBus.ProgressTracker;
|
|
||||||
|
private readonly IHubContext<ProgressHub, IProgressReceiver> _hubContext;
|
||||||
|
private readonly ProgressTrackerService _tracker;
|
||||||
|
|
||||||
|
public ProgressHub(IHubContext<ProgressHub, IProgressReceiver> hubContext, ProgressTrackerService tracker)
|
||||||
|
{
|
||||||
|
_hubContext = hubContext;
|
||||||
|
_tracker = tracker;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> Join(string taskId)
|
public async Task<bool> Join(string taskId)
|
||||||
{
|
{
|
||||||
await Groups.AddToGroupAsync(Context.ConnectionId, taskId);
|
return await Task.Run(() => _tracker.BindTask(taskId, Context.ConnectionId));
|
||||||
|
|
||||||
// 发送当前状态(如果存在)
|
|
||||||
var task = _progressTracker.GetTask(taskId);
|
|
||||||
if (task != null)
|
|
||||||
{
|
|
||||||
await Clients.Caller.OnReceiveProgress(task.Value.ToProgressInfo());
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info($"Client {Context.ConnectionId} joined task {taskId}");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> Leave(string taskId)
|
|
||||||
{
|
|
||||||
await Groups.RemoveFromGroupAsync(Context.ConnectionId, taskId);
|
|
||||||
logger.Info($"Client {Context.ConnectionId} left task {taskId}");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ProgressInfo?> GetProgress(string taskId)
|
|
||||||
{
|
|
||||||
return _progressTracker.GetTask(taskId)?.ToProgressInfo();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
using server.Services;
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 多线程通信总线
|
/// 多线程通信总线
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class MsgBus
|
public static class MsgBus
|
||||||
{
|
{
|
||||||
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
@ -12,39 +11,12 @@ public sealed class MsgBus
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static UDPServer UDPServer { get { return udpServer; } }
|
public static UDPServer UDPServer { get { return udpServer; } }
|
||||||
|
|
||||||
// 添加静态ProgressTracker引用
|
|
||||||
private static ProgressTracker? _progressTracker;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置全局ProgressTracker实例
|
|
||||||
/// </summary>
|
|
||||||
public static void SetProgressTracker(ProgressTracker progressTracker)
|
|
||||||
{
|
|
||||||
_progressTracker = progressTracker;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ProgressTracker ProgressTracker
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_progressTracker == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("ProgressTracker is not set.");
|
|
||||||
}
|
|
||||||
return _progressTracker;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool isRunning = false;
|
private static bool isRunning = false;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取通信总线运行状态
|
/// 获取通信总线运行状态
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool IsRunning { get { return isRunning; } }
|
public static bool IsRunning { get { return isRunning; } }
|
||||||
|
|
||||||
private MsgBus() { }
|
|
||||||
|
|
||||||
static MsgBus() { }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 通信总线初始化
|
/// 通信总线初始化
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -380,7 +380,6 @@ public class JtagStatusReg
|
||||||
public class Jtag
|
public class Jtag
|
||||||
{
|
{
|
||||||
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||||||
private readonly ProgressTracker? _progressTracker;
|
|
||||||
|
|
||||||
private const int CLOCK_FREQ = 50; // MHz
|
private const int CLOCK_FREQ = 50; // MHz
|
||||||
|
|
||||||
|
@ -393,21 +392,18 @@ public class Jtag
|
||||||
public readonly string address;
|
public readonly string address;
|
||||||
private IPEndPoint ep;
|
private IPEndPoint ep;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Jtag 构造函数
|
/// Jtag 构造函数
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">目标 IP 地址</param>
|
/// <param name="address">目标 IP 地址</param>
|
||||||
/// <param name="port">目标 UDP 端口</param>
|
/// <param name="port">目标 UDP 端口</param>
|
||||||
/// <param name="timeout">超时时间(毫秒)</param>
|
/// <param name="timeout">超时时间(毫秒)</param>
|
||||||
/// <param name="progressTracker">进度追踪器</param>
|
public Jtag(string address, int port, int timeout = 2000)
|
||||||
public Jtag(string address, int port, int timeout = 2000, ProgressTracker? progressTracker = null)
|
|
||||||
{
|
{
|
||||||
this.address = address;
|
this.address = address;
|
||||||
this.port = port;
|
this.port = port;
|
||||||
this.ep = new IPEndPoint(IPAddress.Parse(address), port);
|
this.ep = new IPEndPoint(IPAddress.Parse(address), port);
|
||||||
this.timeout = timeout;
|
this.timeout = timeout;
|
||||||
this._progressTracker = progressTracker;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async ValueTask<Result<uint>> ReadFIFO(uint devAddr)
|
async ValueTask<Result<uint>> ReadFIFO(uint devAddr)
|
||||||
|
@ -448,10 +444,10 @@ public class Jtag
|
||||||
|
|
||||||
async ValueTask<Result<bool>> WriteFIFO(
|
async ValueTask<Result<bool>> WriteFIFO(
|
||||||
UInt32 devAddr, UInt32 data, UInt32 result,
|
UInt32 devAddr, UInt32 data, UInt32 result,
|
||||||
UInt32 resultMask = 0xFF_FF_FF_FF, UInt32 delayMilliseconds = 0, string progressId = "")
|
UInt32 resultMask = 0xFF_FF_FF_FF, UInt32 delayMilliseconds = 0, ProgressReporter? progress = null)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
var ret = await UDPClientPool.WriteAddr(this.ep, 0, devAddr, data, this.timeout, progressId);
|
var ret = await UDPClientPool.WriteAddr(this.ep, 0, devAddr, data, this.timeout, progress?.CreateChild(80));
|
||||||
if (!ret.IsSuccessful) return new(ret.Error);
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
if (!ret.Value) return new(new Exception("Write FIFO failed"));
|
if (!ret.Value) return new(new Exception("Write FIFO failed"));
|
||||||
}
|
}
|
||||||
|
@ -462,17 +458,17 @@ public class Jtag
|
||||||
{
|
{
|
||||||
var ret = await UDPClientPool.ReadAddrWithWait(this.ep, 0, JtagAddr.STATE, result, resultMask, 0, this.timeout);
|
var ret = await UDPClientPool.ReadAddrWithWait(this.ep, 0, JtagAddr.STATE, result, resultMask, 0, this.timeout);
|
||||||
if (!ret.IsSuccessful) return new(ret.Error);
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
progress?.Finish();
|
||||||
return ret.Value;
|
return ret.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async ValueTask<Result<bool>> WriteFIFO(
|
async ValueTask<Result<bool>> WriteFIFO(
|
||||||
UInt32 devAddr, byte[] data, UInt32 result,
|
UInt32 devAddr, byte[] data, UInt32 result,
|
||||||
UInt32 resultMask = 0xFF_FF_FF_FF, UInt32 delayMilliseconds = 0, string progressId = "")
|
UInt32 resultMask = 0xFF_FF_FF_FF, UInt32 delayMilliseconds = 0, ProgressReporter? progress = null)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
var ret = await UDPClientPool.WriteAddr(this.ep, 0, devAddr, data, this.timeout, progressId);
|
var ret = await UDPClientPool.WriteAddr(this.ep, 0, devAddr, data, this.timeout, progress?.CreateChild(80));
|
||||||
if (!ret.IsSuccessful) return new(ret.Error);
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
if (!ret.Value) return new(new Exception("Write FIFO failed"));
|
if (!ret.Value) return new(new Exception("Write FIFO failed"));
|
||||||
}
|
}
|
||||||
|
@ -483,7 +479,7 @@ public class Jtag
|
||||||
{
|
{
|
||||||
var ret = await UDPClientPool.ReadAddrWithWait(this.ep, 0, JtagAddr.STATE, result, resultMask, 0, this.timeout);
|
var ret = await UDPClientPool.ReadAddrWithWait(this.ep, 0, JtagAddr.STATE, result, resultMask, 0, this.timeout);
|
||||||
if (!ret.IsSuccessful) return new(ret.Error);
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
progress?.Finish();
|
||||||
return ret.Value;
|
return ret.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -568,7 +564,7 @@ public class Jtag
|
||||||
}
|
}
|
||||||
|
|
||||||
async ValueTask<Result<bool>> LoadDRCareInput(
|
async ValueTask<Result<bool>> LoadDRCareInput(
|
||||||
byte[] bytesArray, UInt32 timeout = 10_000, UInt32 cycle = 500, string progressId = "")
|
byte[] bytesArray, UInt32 timeout = 10_000, UInt32 cycle = 500, ProgressReporter? progress = null)
|
||||||
{
|
{
|
||||||
var bytesLen = ((uint)(bytesArray.Length * 8));
|
var bytesLen = ((uint)(bytesArray.Length * 8));
|
||||||
if (bytesLen > Math.Pow(2, 28)) return new(new Exception("Length is over 2^(28 - 3)"));
|
if (bytesLen > Math.Pow(2, 28)) return new(new Exception("Length is over 2^(28 - 3)"));
|
||||||
|
@ -583,15 +579,14 @@ public class Jtag
|
||||||
else if (!ret.Value) return new(new Exception("Write CMD_JTAG_LOAD_DR_CAREI Failed"));
|
else if (!ret.Value) return new(new Exception("Write CMD_JTAG_LOAD_DR_CAREI Failed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
progress?.Report(10);
|
||||||
|
|
||||||
{
|
{
|
||||||
var ret = await WriteFIFO(
|
var ret = await WriteFIFO(
|
||||||
JtagAddr.WRITE_DATA,
|
JtagAddr.WRITE_DATA,
|
||||||
bytesArray, 0x01_00_00_00,
|
bytesArray, 0x01_00_00_00,
|
||||||
JtagState.CMD_EXEC_FINISH,
|
JtagState.CMD_EXEC_FINISH,
|
||||||
0,
|
progress: progress?.CreateChild(90)
|
||||||
progressId
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!ret.IsSuccessful) return new(ret.Error);
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
|
@ -714,53 +709,58 @@ public class Jtag
|
||||||
/// 下载比特流到 JTAG 设备
|
/// 下载比特流到 JTAG 设备
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="bitstream">比特流数据</param>
|
/// <param name="bitstream">比特流数据</param>
|
||||||
/// <param name="progressId">进度ID</param>
|
/// <param name="progress">进度报告器</param>
|
||||||
/// <returns>指示下载是否成功的异步结果</returns>
|
/// <returns>指示下载是否成功的异步结果</returns>
|
||||||
public async ValueTask<Result<bool>> DownloadBitstream(
|
public async ValueTask<Result<bool>> DownloadBitstream(
|
||||||
byte[] bitstream, string progressId = "")
|
byte[] bitstream, ProgressReporter? progress = null)
|
||||||
{
|
{
|
||||||
// Clear Data
|
// Clear Data
|
||||||
MsgBus.UDPServer.ClearUDPData(this.address, 0);
|
MsgBus.UDPServer.ClearUDPData(this.address, 0);
|
||||||
|
|
||||||
logger.Trace($"Clear up udp server {this.address,0} receive data");
|
logger.Trace($"Clear up udp server {this.address,0} receive data");
|
||||||
|
if (progress != null)
|
||||||
|
{
|
||||||
|
progress.ExpectedSteps = 25;
|
||||||
|
progress.Increase();
|
||||||
|
}
|
||||||
|
|
||||||
Result<bool> ret;
|
Result<bool> ret;
|
||||||
|
|
||||||
ret = await CloseTest();
|
ret = await CloseTest();
|
||||||
if (!ret.IsSuccessful) return new(ret.Error);
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
else if (!ret.Value) return new(new Exception("Jtag Close Test Failed"));
|
else if (!ret.Value) return new(new Exception("Jtag Close Test Failed"));
|
||||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
progress?.Increase();
|
||||||
|
|
||||||
ret = await RunTest();
|
ret = await RunTest();
|
||||||
if (!ret.IsSuccessful) return new(ret.Error);
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
else if (!ret.Value) return new(new Exception("Jtag Run Test Failed"));
|
else if (!ret.Value) return new(new Exception("Jtag Run Test Failed"));
|
||||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
progress?.Increase();
|
||||||
|
|
||||||
logger.Trace("Jtag initialize");
|
logger.Trace("Jtag initialize");
|
||||||
|
|
||||||
ret = await ExecRDCmd(JtagCmd.JTAG_DR_JRST);
|
ret = await ExecRDCmd(JtagCmd.JTAG_DR_JRST);
|
||||||
if (!ret.IsSuccessful) return new(ret.Error);
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
else if (!ret.Value) return new(new Exception("Jtag Execute Command JTAG_DR_JRST Failed"));
|
else if (!ret.Value) return new(new Exception("Jtag Execute Command JTAG_DR_JRST Failed"));
|
||||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
progress?.Increase();
|
||||||
|
|
||||||
ret = await RunTest();
|
ret = await RunTest();
|
||||||
if (!ret.IsSuccessful) return new(ret.Error);
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
else if (!ret.Value) return new(new Exception("Jtag Run Test Failed"));
|
else if (!ret.Value) return new(new Exception("Jtag Run Test Failed"));
|
||||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
progress?.Increase();
|
||||||
|
|
||||||
ret = await ExecRDCmd(JtagCmd.JTAG_DR_CFGI);
|
ret = await ExecRDCmd(JtagCmd.JTAG_DR_CFGI);
|
||||||
if (!ret.IsSuccessful) return new(ret.Error);
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
else if (!ret.Value) return new(new Exception("Jtag Execute Command JTAG_DR_CFGI Failed"));
|
else if (!ret.Value) return new(new Exception("Jtag Execute Command JTAG_DR_CFGI Failed"));
|
||||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
progress?.Increase();
|
||||||
|
|
||||||
logger.Trace("Jtag ready to write bitstream");
|
logger.Trace("Jtag ready to write bitstream");
|
||||||
|
|
||||||
ret = await IdleDelay(100000);
|
ret = await IdleDelay(100000);
|
||||||
if (!ret.IsSuccessful) return new(ret.Error);
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
else if (!ret.Value) return new(new Exception("Jtag IDLE Delay Failed"));
|
else if (!ret.Value) return new(new Exception("Jtag IDLE Delay Failed"));
|
||||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
progress?.Increase();
|
||||||
|
|
||||||
ret = await LoadDRCareInput(bitstream, progressId: progressId);
|
ret = await LoadDRCareInput(bitstream, progress: progress?.CreateChild(50));
|
||||||
if (!ret.IsSuccessful) return new(ret.Error);
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
else if (!ret.Value) return new(new Exception("Jtag Load Data Failed"));
|
else if (!ret.Value) return new(new Exception("Jtag Load Data Failed"));
|
||||||
|
|
||||||
|
@ -769,40 +769,40 @@ public class Jtag
|
||||||
ret = await CloseTest();
|
ret = await CloseTest();
|
||||||
if (!ret.IsSuccessful) return new(ret.Error);
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
else if (!ret.Value) return new(new Exception("Jtag Close Test Failed"));
|
else if (!ret.Value) return new(new Exception("Jtag Close Test Failed"));
|
||||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
progress?.Increase();
|
||||||
|
|
||||||
ret = await RunTest();
|
ret = await RunTest();
|
||||||
if (!ret.IsSuccessful) return new(ret.Error);
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
else if (!ret.Value) return new(new Exception("Jtag Run Test Failed"));
|
else if (!ret.Value) return new(new Exception("Jtag Run Test Failed"));
|
||||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
progress?.Increase();
|
||||||
|
|
||||||
ret = await ExecRDCmd(JtagCmd.JTAG_DR_JWAKEUP);
|
ret = await ExecRDCmd(JtagCmd.JTAG_DR_JWAKEUP);
|
||||||
if (!ret.IsSuccessful) return new(ret.Error);
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
else if (!ret.Value) return new(new Exception("Jtag Execute Command JTAG_DR_JWAKEUP Failed"));
|
else if (!ret.Value) return new(new Exception("Jtag Execute Command JTAG_DR_JWAKEUP Failed"));
|
||||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
progress?.Increase();
|
||||||
|
|
||||||
logger.Trace("Jtag reset device");
|
logger.Trace("Jtag reset device");
|
||||||
|
|
||||||
ret = await IdleDelay(10000);
|
ret = await IdleDelay(10000);
|
||||||
if (!ret.IsSuccessful) return new(ret.Error);
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
else if (!ret.Value) return new(new Exception("Jtag IDLE Delay Failed"));
|
else if (!ret.Value) return new(new Exception("Jtag IDLE Delay Failed"));
|
||||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
progress?.Increase();
|
||||||
|
|
||||||
var retCode = await ReadStatusReg();
|
var retCode = await ReadStatusReg();
|
||||||
if (!retCode.IsSuccessful) return new(retCode.Error);
|
if (!retCode.IsSuccessful) return new(retCode.Error);
|
||||||
var jtagStatus = new JtagStatusReg(retCode.Value);
|
var jtagStatus = new JtagStatusReg(retCode.Value);
|
||||||
if (!(jtagStatus.done && jtagStatus.wakeup_over && jtagStatus.init_complete))
|
if (!(jtagStatus.done && jtagStatus.wakeup_over && jtagStatus.init_complete))
|
||||||
return new(new Exception("Jtag download bitstream failed"));
|
return new(new Exception("Jtag download bitstream failed"));
|
||||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
progress?.Increase();
|
||||||
|
|
||||||
ret = await CloseTest();
|
ret = await CloseTest();
|
||||||
if (!ret.IsSuccessful) return new(ret.Error);
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
else if (!ret.Value) return new(new Exception("Jtag Close Test Failed"));
|
else if (!ret.Value) return new(new Exception("Jtag Close Test Failed"));
|
||||||
logger.Trace("Jtag download bitstream successfully");
|
logger.Trace("Jtag download bitstream successfully");
|
||||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
progress?.Increase();
|
||||||
|
|
||||||
// Finish
|
// Finish
|
||||||
_progressTracker?.AdvanceProgress(progressId, 10);
|
progress?.Finish();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,147 +0,0 @@
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using Microsoft.AspNetCore.SignalR;
|
|
||||||
using server.Hubs;
|
|
||||||
|
|
||||||
namespace server.Services;
|
|
||||||
|
|
||||||
public enum TaskState { Running, Completed, Failed, Cancelled }
|
|
||||||
|
|
||||||
public readonly struct TaskProgress
|
|
||||||
{
|
|
||||||
public string Id { get; }
|
|
||||||
public int Current { get; }
|
|
||||||
public int Total { get; }
|
|
||||||
public TaskState State { get; }
|
|
||||||
public long Timestamp { get; }
|
|
||||||
public string? Error { get; }
|
|
||||||
|
|
||||||
public TaskProgress(string id, int current, int total, TaskState state, long timestamp, string? error = null)
|
|
||||||
{
|
|
||||||
Id = id;
|
|
||||||
Current = current;
|
|
||||||
Total = total;
|
|
||||||
State = state;
|
|
||||||
Timestamp = timestamp;
|
|
||||||
Error = error;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TaskProgress WithUpdate(int? current = null, TaskState? state = null, string? error = null)
|
|
||||||
{
|
|
||||||
return new TaskProgress(
|
|
||||||
Id,
|
|
||||||
current ?? Current,
|
|
||||||
Total,
|
|
||||||
state ?? State,
|
|
||||||
DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
|
||||||
error ?? Error
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProgressInfo ToProgressInfo()
|
|
||||||
{
|
|
||||||
return new ProgressInfo
|
|
||||||
{
|
|
||||||
TaskId = Id,
|
|
||||||
Status = State switch
|
|
||||||
{
|
|
||||||
TaskState.Running => ProgressStatus.Running,
|
|
||||||
TaskState.Completed => ProgressStatus.Completed,
|
|
||||||
TaskState.Failed => ProgressStatus.Failed,
|
|
||||||
TaskState.Cancelled => ProgressStatus.Canceled,
|
|
||||||
_ => ProgressStatus.Failed
|
|
||||||
},
|
|
||||||
ProgressPercent = Total > 0 ? (Current * 100) / Total : 0,
|
|
||||||
ErrorMessage = Error ?? string.Empty
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class ProgressTracker
|
|
||||||
{
|
|
||||||
private readonly ConcurrentDictionary<string, TaskProgress> _tasks = new();
|
|
||||||
private readonly Timer _cleaner;
|
|
||||||
private readonly IHubContext<ProgressHub, IProgressReceiver> _hubContext;
|
|
||||||
|
|
||||||
// 构造器支持可选的Hub注入
|
|
||||||
public ProgressTracker(IHubContext<ProgressHub, IProgressReceiver> hubContext)
|
|
||||||
{
|
|
||||||
_hubContext = hubContext;
|
|
||||||
_cleaner = new Timer(CleanExpiredTasks, null,
|
|
||||||
TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CleanExpiredTasks(object? obj)
|
|
||||||
{
|
|
||||||
var cutoff = DateTimeOffset.Now.AddMinutes(-3).ToUnixTimeSeconds();
|
|
||||||
var expired = _tasks.Where(kvp => kvp.Value.Timestamp < cutoff).Select(kvp => kvp.Key).ToList();
|
|
||||||
foreach (var id in expired)
|
|
||||||
{
|
|
||||||
_tasks.TryRemove(id, out _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string CreateTask(int total)
|
|
||||||
{
|
|
||||||
var id = Guid.NewGuid().ToString();
|
|
||||||
var task = new TaskProgress(id, 0, total, TaskState.Running, DateTimeOffset.UtcNow.ToUnixTimeSeconds());
|
|
||||||
_tasks[id] = task;
|
|
||||||
NotifyIfNeeded(task);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 核心更新方法,现在包含自动通知
|
|
||||||
public bool UpdateTask(string id, Func<TaskProgress, TaskProgress> updater)
|
|
||||||
{
|
|
||||||
if (!_tasks.TryGetValue(id, out var current))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var updated = updater(current);
|
|
||||||
if (_tasks.TryUpdate(id, updated, current))
|
|
||||||
{
|
|
||||||
NotifyIfNeeded(updated);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 自动通知逻辑 - 简单直接
|
|
||||||
private void NotifyIfNeeded(TaskProgress task)
|
|
||||||
{
|
|
||||||
_hubContext.Clients.Group(task.Id).OnReceiveProgress(task.ToProgressInfo());
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool UpdateProgress(string id, int current)
|
|
||||||
{
|
|
||||||
return UpdateTask(id, p => p.WithUpdate(
|
|
||||||
current: Math.Min(current, p.Total)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool AdvanceProgress(string id, int steps)
|
|
||||||
{
|
|
||||||
return UpdateTask(id, p => p.WithUpdate(
|
|
||||||
current: Math.Min(p.Current + steps, p.Total)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CancelProgress(string id)
|
|
||||||
{
|
|
||||||
return UpdateTask(id, p => p.WithUpdate(state: TaskState.Cancelled));
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CompleteProgress(string id)
|
|
||||||
{
|
|
||||||
return UpdateTask(id, p => p.WithUpdate(
|
|
||||||
current: p.Total, state: TaskState.Completed));
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool FailProgress(string id, string? error)
|
|
||||||
{
|
|
||||||
return UpdateTask(id, p => p.WithUpdate(
|
|
||||||
state: TaskState.Failed, error: error));
|
|
||||||
}
|
|
||||||
|
|
||||||
public TaskProgress? GetTask(string id)
|
|
||||||
{
|
|
||||||
_tasks.TryGetValue(id, out var task);
|
|
||||||
return task.Id == null ? null : task;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,294 @@
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using DotNext;
|
||||||
|
using Common;
|
||||||
|
using server.Hubs;
|
||||||
|
|
||||||
|
namespace server.Services;
|
||||||
|
|
||||||
|
public class ProgressReporter : ProgressInfo, IProgress<int>
|
||||||
|
{
|
||||||
|
private int _progress = 0;
|
||||||
|
private int _stepProgress = 1;
|
||||||
|
private int _expectedSteps = 100;
|
||||||
|
private int _parentProportion = 100;
|
||||||
|
|
||||||
|
public int Progress => _progress;
|
||||||
|
public int MaxProgress { get; set; } = 100;
|
||||||
|
public int StepProgress
|
||||||
|
{
|
||||||
|
get => _stepProgress;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_stepProgress = value;
|
||||||
|
_expectedSteps = MaxProgress / value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public int ExpectedSteps
|
||||||
|
{
|
||||||
|
get => _expectedSteps;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_expectedSteps = value;
|
||||||
|
MaxProgress = Number.IntPow(10, Number.GetLength(value));
|
||||||
|
_stepProgress = MaxProgress / value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Func<int, Task>? ReporterFunc { get; set; } = null;
|
||||||
|
public ProgressReporter? Parent { get; set; }
|
||||||
|
public ProgressReporter? Child { get; set; }
|
||||||
|
|
||||||
|
private ProgressStatus _status = ProgressStatus.Pending;
|
||||||
|
private string _errorMessage = string.Empty;
|
||||||
|
|
||||||
|
public override string TaskId { get; } = Guid.NewGuid().ToString();
|
||||||
|
public override int ProgressPercent => _progress * 100 / MaxProgress;
|
||||||
|
public override ProgressStatus Status => _status;
|
||||||
|
public override string ErrorMessage => _errorMessage;
|
||||||
|
|
||||||
|
public ProgressReporter(Func<int, Task>? reporter = null, int initProgress = 0, int maxProgress = 100, int step = 1)
|
||||||
|
{
|
||||||
|
_progress = initProgress;
|
||||||
|
MaxProgress = maxProgress;
|
||||||
|
StepProgress = step;
|
||||||
|
ReporterFunc = reporter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProgressReporter(int parentProportion, int expectedSteps = 100, Func<int, Task>? reporter = null)
|
||||||
|
{
|
||||||
|
this._parentProportion = parentProportion;
|
||||||
|
MaxProgress = Number.IntPow(10, Number.GetLength(expectedSteps));
|
||||||
|
StepProgress = MaxProgress / expectedSteps;
|
||||||
|
ReporterFunc = reporter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void ForceReport(int value)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (ReporterFunc != null)
|
||||||
|
await ReporterFunc(value);
|
||||||
|
|
||||||
|
if (Parent != null)
|
||||||
|
Parent.Increase((value - _progress) / StepProgress * _parentProportion / (MaxProgress / StepProgress));
|
||||||
|
|
||||||
|
_progress = value;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException ex)
|
||||||
|
{
|
||||||
|
_errorMessage = ex.Message;
|
||||||
|
this._status = ProgressStatus.Canceled;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_errorMessage = ex.Message;
|
||||||
|
this._status = ProgressStatus.Failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Report(int value)
|
||||||
|
{
|
||||||
|
if (this._status == ProgressStatus.Pending)
|
||||||
|
this._status = ProgressStatus.InProgress;
|
||||||
|
else if (this.Status != ProgressStatus.InProgress)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (value > MaxProgress) return;
|
||||||
|
ForceReport(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Increase(int? value = null)
|
||||||
|
{
|
||||||
|
if (this._status == ProgressStatus.Pending)
|
||||||
|
this._status = ProgressStatus.InProgress;
|
||||||
|
else if (this.Status != ProgressStatus.InProgress)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (value.HasValue)
|
||||||
|
{
|
||||||
|
if (_progress + value.Value >= MaxProgress) return;
|
||||||
|
this.Report(_progress + value.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_progress + StepProgress >= MaxProgress) return;
|
||||||
|
this.Report(_progress + StepProgress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Finish()
|
||||||
|
{
|
||||||
|
this._status = ProgressStatus.Completed;
|
||||||
|
this.ForceReport(MaxProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Cancel()
|
||||||
|
{
|
||||||
|
this._status = ProgressStatus.Canceled;
|
||||||
|
this._errorMessage = "User Cancelled";
|
||||||
|
this.ForceReport(_progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Error(string message)
|
||||||
|
{
|
||||||
|
this._status = ProgressStatus.Failed;
|
||||||
|
this._errorMessage = message;
|
||||||
|
this.ForceReport(_progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProgressReporter CreateChild(int proportion, int expectedSteps = 100)
|
||||||
|
{
|
||||||
|
var child = new ProgressReporter(proportion, expectedSteps);
|
||||||
|
child.Parent = this;
|
||||||
|
this.Child = child;
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ProgressTrackerService : BackgroundService
|
||||||
|
{
|
||||||
|
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||||||
|
private readonly ConcurrentDictionary<string, TaskProgressInfo> _taskMap = new();
|
||||||
|
private readonly IHubContext<ProgressHub, IProgressReceiver> _hubContext;
|
||||||
|
|
||||||
|
private class TaskProgressInfo
|
||||||
|
{
|
||||||
|
public ProgressReporter? Reporter { get; set; }
|
||||||
|
public string? ConnectionId { get; set; }
|
||||||
|
public required CancellationToken CancellationToken { get; set; }
|
||||||
|
public required CancellationTokenSource CancellationTokenSource { get; set; }
|
||||||
|
public required DateTime UpdatedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProgressTrackerService(IHubContext<ProgressHub, IProgressReceiver> hubContext)
|
||||||
|
{
|
||||||
|
_hubContext = hubContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
foreach (var kvp in _taskMap)
|
||||||
|
{
|
||||||
|
var info = kvp.Value;
|
||||||
|
// 超过 1 分钟且任务已完成/失败/取消
|
||||||
|
if (
|
||||||
|
(now - info.UpdatedAt).TotalMinutes > 1 &&
|
||||||
|
info.Reporter != null &&
|
||||||
|
(
|
||||||
|
info.Reporter.Status == ProgressStatus.Completed ||
|
||||||
|
info.Reporter.Status == ProgressStatus.Failed ||
|
||||||
|
info.Reporter.Status == ProgressStatus.Canceled
|
||||||
|
)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_taskMap.TryRemove(kvp.Key, out _);
|
||||||
|
logger.Info($"Cleaned up task {kvp.Key}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.Error(ex, "Error during ProgressTracker cleanup");
|
||||||
|
}
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public (string, ProgressReporter) CreateTask(CancellationToken? cancellationToken = null)
|
||||||
|
{
|
||||||
|
CancellationTokenSource? cancellationTokenSource;
|
||||||
|
if (cancellationToken.HasValue)
|
||||||
|
{
|
||||||
|
cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
var progressInfo = new TaskProgressInfo
|
||||||
|
{
|
||||||
|
ConnectionId = null,
|
||||||
|
UpdatedAt = DateTime.UtcNow,
|
||||||
|
CancellationToken = cancellationTokenSource.Token,
|
||||||
|
CancellationTokenSource = cancellationTokenSource,
|
||||||
|
};
|
||||||
|
|
||||||
|
var progress = new ProgressReporter(async value =>
|
||||||
|
{
|
||||||
|
cancellationTokenSource.Token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
// 通过 SignalR 推送进度
|
||||||
|
if (progressInfo.ConnectionId != null && progressInfo.Reporter != null)
|
||||||
|
await _hubContext.Clients.Client(progressInfo.ConnectionId).OnReceiveProgress(progressInfo.Reporter);
|
||||||
|
});
|
||||||
|
|
||||||
|
progressInfo.Reporter = progress;
|
||||||
|
|
||||||
|
_taskMap.TryAdd(progressInfo.Reporter.TaskId, progressInfo);
|
||||||
|
|
||||||
|
return (progressInfo.Reporter.TaskId, progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<ProgressReporter> GetReporter(string taskId)
|
||||||
|
{
|
||||||
|
if (_taskMap.TryGetValue(taskId, out var info))
|
||||||
|
{
|
||||||
|
return info.Reporter;
|
||||||
|
}
|
||||||
|
return Optional<ProgressReporter>.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<ProgressStatus> GetProgressStatus(string taskId)
|
||||||
|
{
|
||||||
|
if (_taskMap.TryGetValue(taskId, out var info))
|
||||||
|
{
|
||||||
|
if (info.Reporter != null)
|
||||||
|
return info.Reporter.Status;
|
||||||
|
}
|
||||||
|
return Optional<ProgressStatus>.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool BindTask(string taskId, string connectionId)
|
||||||
|
{
|
||||||
|
if (_taskMap.TryGetValue(taskId, out var info) && info != null)
|
||||||
|
{
|
||||||
|
lock (info)
|
||||||
|
{
|
||||||
|
info.ConnectionId = connectionId;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CancelTask(string taskId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_taskMap.TryGetValue(taskId, out var info) && info != null && info.Reporter != null)
|
||||||
|
{
|
||||||
|
lock (info)
|
||||||
|
{
|
||||||
|
info.CancellationTokenSource.Cancel();
|
||||||
|
info.Reporter.Cancel();
|
||||||
|
info.UpdatedAt = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.Error(ex, $"Failed to cancel task {taskId}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,11 +8,11 @@ using server.Services;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// UDP客户端发送池
|
/// UDP客户端发送池
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class UDPClientPool
|
public class UDPClientPool
|
||||||
{
|
{
|
||||||
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
private static ProgressTracker _progressTracker = MsgBus.ProgressTracker;
|
private static IPAddress localhost = IPAddress.Parse("127.0.0.1");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发送字符串
|
/// 发送字符串
|
||||||
|
@ -183,6 +183,40 @@ public sealed class UDPClientPool
|
||||||
return await Task.Run(() => { return SendDataPack(endPoint, pkg); });
|
return await Task.Run(() => { return SendDataPack(endPoint, pkg); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送字符串到本地
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="port">端口</param>
|
||||||
|
/// <param name="stringArray">字符串数组</param>
|
||||||
|
/// <returns>是否成功</returns>
|
||||||
|
public static bool SendStringLocalHost(int port, string[] stringArray)
|
||||||
|
{
|
||||||
|
return SendString(new IPEndPoint(localhost, port), stringArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 循环发送字符串到本地
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="times">发送总次数</param>
|
||||||
|
/// <param name="sleepMilliSeconds">间隔时间</param>
|
||||||
|
/// <param name="port">端口</param>
|
||||||
|
/// <param name="stringArray">字符串数组</param>
|
||||||
|
/// <returns>是否成功</returns>
|
||||||
|
public static bool CycleSendStringLocalHost(int times, int sleepMilliSeconds, int port, string[] stringArray)
|
||||||
|
{
|
||||||
|
var isSuccessful = true;
|
||||||
|
|
||||||
|
while (times-- >= 0)
|
||||||
|
{
|
||||||
|
isSuccessful = SendStringLocalHost(port, stringArray);
|
||||||
|
if (!isSuccessful) break;
|
||||||
|
|
||||||
|
Thread.Sleep(sleepMilliSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 读取设备地址数据
|
/// 读取设备地址数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -573,11 +607,11 @@ public sealed class UDPClientPool
|
||||||
/// <param name="devAddr">设备地址</param>
|
/// <param name="devAddr">设备地址</param>
|
||||||
/// <param name="data">要写入的32位数据</param>
|
/// <param name="data">要写入的32位数据</param>
|
||||||
/// <param name="timeout">超时时间(毫秒)</param>
|
/// <param name="timeout">超时时间(毫秒)</param>
|
||||||
/// <param name="progressId">进度报告器</param>
|
/// <param name="progress">进度报告器</param>
|
||||||
/// <returns>写入结果,true表示写入成功</returns>
|
/// <returns>写入结果,true表示写入成功</returns>
|
||||||
public static async ValueTask<Result<bool>> WriteAddr(
|
public static async ValueTask<Result<bool>> WriteAddr(
|
||||||
IPEndPoint endPoint, int taskID, UInt32 devAddr,
|
IPEndPoint endPoint, int taskID, UInt32 devAddr,
|
||||||
UInt32 data, int timeout = 1000, string progressId = "")
|
UInt32 data, int timeout = 1000, ProgressReporter? progress = null)
|
||||||
{
|
{
|
||||||
var ret = false;
|
var ret = false;
|
||||||
var opts = new SendAddrPackOptions()
|
var opts = new SendAddrPackOptions()
|
||||||
|
@ -588,18 +622,17 @@ public sealed class UDPClientPool
|
||||||
Address = devAddr,
|
Address = devAddr,
|
||||||
IsWrite = true,
|
IsWrite = true,
|
||||||
};
|
};
|
||||||
_progressTracker.AdvanceProgress(progressId, 10);
|
progress?.Report(20);
|
||||||
|
|
||||||
// Write Register
|
// Write Register
|
||||||
ret = await UDPClientPool.SendAddrPackAsync(endPoint, new SendAddrPackage(opts));
|
ret = await UDPClientPool.SendAddrPackAsync(endPoint, new SendAddrPackage(opts));
|
||||||
if (!ret) return new(new Exception("Send 1st address package failed!"));
|
if (!ret) return new(new Exception("Send 1st address package failed!"));
|
||||||
_progressTracker.AdvanceProgress(progressId, 10);
|
progress?.Report(40);
|
||||||
|
|
||||||
// Send Data Package
|
// Send Data Package
|
||||||
ret = await UDPClientPool.SendDataPackAsync(endPoint,
|
ret = await UDPClientPool.SendDataPackAsync(endPoint,
|
||||||
new SendDataPackage(Common.Number.NumberToBytes(data, 4).Value));
|
new SendDataPackage(Common.Number.NumberToBytes(data, 4).Value));
|
||||||
if (!ret) return new(new Exception("Send data package failed!"));
|
if (!ret) return new(new Exception("Send data package failed!"));
|
||||||
_progressTracker.AdvanceProgress(progressId, 10);
|
progress?.Report(60);
|
||||||
|
|
||||||
// Check Msg Bus
|
// Check Msg Bus
|
||||||
if (!MsgBus.IsRunning)
|
if (!MsgBus.IsRunning)
|
||||||
|
@ -609,7 +642,7 @@ public sealed class UDPClientPool
|
||||||
var udpWriteAck = await MsgBus.UDPServer.WaitForAckAsync(
|
var udpWriteAck = await MsgBus.UDPServer.WaitForAckAsync(
|
||||||
endPoint.Address.ToString(), taskID, endPoint.Port, timeout);
|
endPoint.Address.ToString(), taskID, endPoint.Port, timeout);
|
||||||
if (!udpWriteAck.IsSuccessful) return new(udpWriteAck.Error);
|
if (!udpWriteAck.IsSuccessful) return new(udpWriteAck.Error);
|
||||||
_progressTracker.AdvanceProgress(progressId, 10);
|
progress?.Finish();
|
||||||
|
|
||||||
return udpWriteAck.Value.IsSuccessful;
|
return udpWriteAck.Value.IsSuccessful;
|
||||||
}
|
}
|
||||||
|
@ -622,11 +655,11 @@ public sealed class UDPClientPool
|
||||||
/// <param name="devAddr">设备地址</param>
|
/// <param name="devAddr">设备地址</param>
|
||||||
/// <param name="dataArray">要写入的字节数组</param>
|
/// <param name="dataArray">要写入的字节数组</param>
|
||||||
/// <param name="timeout">超时时间(毫秒)</param>
|
/// <param name="timeout">超时时间(毫秒)</param>
|
||||||
/// <param name="progressId">进度报告器</param>
|
/// <param name="progress">进度报告器</param>
|
||||||
/// <returns>写入结果,true表示写入成功</returns>
|
/// <returns>写入结果,true表示写入成功</returns>
|
||||||
public static async ValueTask<Result<bool>> WriteAddr(
|
public static async ValueTask<Result<bool>> WriteAddr(
|
||||||
IPEndPoint endPoint, int taskID, UInt32 devAddr,
|
IPEndPoint endPoint, int taskID, UInt32 devAddr,
|
||||||
byte[] dataArray, int timeout = 1000, string progressId = "")
|
byte[] dataArray, int timeout = 1000, ProgressReporter? progress = null)
|
||||||
{
|
{
|
||||||
var ret = false;
|
var ret = false;
|
||||||
var opts = new SendAddrPackOptions()
|
var opts = new SendAddrPackOptions()
|
||||||
|
@ -648,6 +681,8 @@ public sealed class UDPClientPool
|
||||||
var writeTimes = hasRest ?
|
var writeTimes = hasRest ?
|
||||||
dataArray.Length / (max4BytesPerRead * (32 / 8)) + 1 :
|
dataArray.Length / (max4BytesPerRead * (32 / 8)) + 1 :
|
||||||
dataArray.Length / (max4BytesPerRead * (32 / 8));
|
dataArray.Length / (max4BytesPerRead * (32 / 8));
|
||||||
|
if (progress != null)
|
||||||
|
progress.ExpectedSteps = writeTimes;
|
||||||
for (var i = 0; i < writeTimes; i++)
|
for (var i = 0; i < writeTimes; i++)
|
||||||
{
|
{
|
||||||
// Sperate Data Array
|
// Sperate Data Array
|
||||||
|
@ -677,9 +712,10 @@ public sealed class UDPClientPool
|
||||||
if (!udpWriteAck.Value.IsSuccessful)
|
if (!udpWriteAck.Value.IsSuccessful)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
_progressTracker.AdvanceProgress(progressId, 10);
|
progress?.Increase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
progress?.Finish();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,32 +16,22 @@
|
||||||
<span class="text-sm">{{ bitstream.name }}</span>
|
<span class="text-sm">{{ bitstream.name }}</span>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<button
|
<button
|
||||||
@click="handleExampleBitstream('download', bitstream)"
|
@click="downloadExampleBitstream(bitstream)"
|
||||||
class="btn btn-sm btn-secondary"
|
class="btn btn-sm btn-secondary"
|
||||||
:disabled="currentTask !== 'none'"
|
:disabled="isDownloading || isProgramming"
|
||||||
>
|
>
|
||||||
<div
|
<div v-if="isDownloading">
|
||||||
v-if="
|
|
||||||
currentTask === 'downloading' &&
|
|
||||||
currentBitstreamId === bitstream.id
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<span class="loading loading-spinner loading-xs"></span>
|
<span class="loading loading-spinner loading-xs"></span>
|
||||||
下载中...
|
{{ downloadProgress }}%
|
||||||
</div>
|
</div>
|
||||||
<div v-else>下载示例</div>
|
<div v-else>下载示例</div>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="handleExampleBitstream('program', bitstream)"
|
@click="programExampleBitstream(bitstream)"
|
||||||
class="btn btn-sm btn-primary"
|
class="btn btn-sm btn-primary"
|
||||||
:disabled="currentTask !== 'none'"
|
:disabled="isDownloading || isProgramming"
|
||||||
>
|
>
|
||||||
<div
|
<div v-if="isProgramming">
|
||||||
v-if="
|
|
||||||
currentTask === 'programming' &&
|
|
||||||
currentBitstreamId === bitstream.id
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<span class="loading loading-spinner loading-xs"></span>
|
<span class="loading loading-spinner loading-xs"></span>
|
||||||
烧录中...
|
烧录中...
|
||||||
</div>
|
</div>
|
||||||
|
@ -73,18 +63,14 @@
|
||||||
<!-- Upload Button -->
|
<!-- Upload Button -->
|
||||||
<div class="card-actions w-full">
|
<div class="card-actions w-full">
|
||||||
<button
|
<button
|
||||||
@click="handleUploadAndDownload"
|
@click="handleClick"
|
||||||
class="btn btn-primary grow"
|
class="btn btn-primary grow"
|
||||||
:disabled="currentTask !== 'none'"
|
:disabled="isUploading || isProgramming"
|
||||||
>
|
>
|
||||||
<div v-if="currentTask === 'uploading'">
|
<div v-if="isUploading">
|
||||||
<span class="loading loading-spinner"></span>
|
<span class="loading loading-spinner"></span>
|
||||||
上传中...
|
上传中...
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="currentTask === 'programming'">
|
|
||||||
<span class="loading loading-spinner"></span>
|
|
||||||
{{ currentProgressPercent }}% ...
|
|
||||||
</div>
|
|
||||||
<div v-else>上传并下载</div>
|
<div v-else>上传并下载</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -92,19 +78,28 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, useTemplateRef, onMounted } from "vue";
|
import { computed, ref, useTemplateRef, onMounted } from "vue";
|
||||||
import { AuthManager } from "@/utils/AuthManager";
|
import { AuthManager } from "@/utils/AuthManager"; // Adjust the path based on your project structure
|
||||||
import { useDialogStore } from "@/stores/dialog";
|
import { useDialogStore } from "@/stores/dialog";
|
||||||
|
import { isNull, isUndefined } from "lodash";
|
||||||
import { useEquipments } from "@/stores/equipments";
|
import { useEquipments } from "@/stores/equipments";
|
||||||
|
import type { HubConnection } from "@microsoft/signalr";
|
||||||
|
import type {
|
||||||
|
IProgressHub,
|
||||||
|
IProgressReceiver,
|
||||||
|
} from "@/utils/signalR/TypedSignalR.Client/server.Hubs";
|
||||||
|
import {
|
||||||
|
getHubProxyFactory,
|
||||||
|
getReceiverRegister,
|
||||||
|
} from "@/utils/signalR/TypedSignalR.Client";
|
||||||
|
import { ProgressStatus } from "@/utils/signalR/server.Hubs";
|
||||||
import { useRequiredInjection } from "@/utils/Common";
|
import { useRequiredInjection } from "@/utils/Common";
|
||||||
import { useAlertStore } from "./Alert";
|
import { useAlertStore } from "./Alert";
|
||||||
import { ResourceClient, ResourcePurpose } from "@/APIClient";
|
import { ResourceClient, ResourcePurpose } from "@/APIClient";
|
||||||
import { useProgressStore } from "@/stores/progress";
|
|
||||||
import { ProgressStatus, type ProgressInfo } from "@/utils/signalR/server.Hubs";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
maxMemory?: number;
|
maxMemory?: number;
|
||||||
examId?: string;
|
examId?: string; // 新增examId属性
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
@ -117,82 +112,71 @@ const emits = defineEmits<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const alert = useRequiredInjection(useAlertStore);
|
const alert = useRequiredInjection(useAlertStore);
|
||||||
const progressTracker = useProgressStore();
|
|
||||||
const dialog = useDialogStore();
|
const dialog = useDialogStore();
|
||||||
const eqps = useEquipments();
|
const eqps = useEquipments();
|
||||||
|
|
||||||
|
const isUploading = ref(false);
|
||||||
|
const isDownloading = ref(false);
|
||||||
|
const isProgramming = ref(false);
|
||||||
const availableBitstreams = ref<{ id: string; name: string }[]>([]);
|
const availableBitstreams = ref<{ id: string; name: string }[]>([]);
|
||||||
const fileInput = useTemplateRef("fileInput");
|
|
||||||
const bitstream = ref<File | undefined>(undefined);
|
|
||||||
|
|
||||||
// 用一个状态变量替代多个
|
|
||||||
const currentTask = ref<"none" | "uploading" | "downloading" | "programming">(
|
|
||||||
"none",
|
|
||||||
);
|
|
||||||
const currentBitstreamId = ref<string>("");
|
|
||||||
const currentProgressId = ref<string>("");
|
|
||||||
const currentProgressPercent = ref<number>(0);
|
|
||||||
|
|
||||||
|
// Progress
|
||||||
|
const downloadTaskId = ref("");
|
||||||
|
const downloadProgress = ref(0);
|
||||||
|
const progressHubConnection = ref<HubConnection>();
|
||||||
|
const progressHubProxy = ref<IProgressHub>();
|
||||||
|
const progressHubReceiver: IProgressReceiver = {
|
||||||
|
onReceiveProgress: async (msg) => {
|
||||||
|
if (msg.taskId == downloadTaskId.value) {
|
||||||
|
if (msg.status == ProgressStatus.InProgress) {
|
||||||
|
downloadProgress.value = msg.progressPercent;
|
||||||
|
} else if (msg.status == ProgressStatus.Failed) {
|
||||||
|
dialog.error(msg.errorMessage);
|
||||||
|
} else if (msg.status == ProgressStatus.Completed) {
|
||||||
|
alert.info("比特流下载成功");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (bitstream.value && fileInput.value) {
|
progressHubConnection.value = AuthManager.createHubConnection("ProgressHub");
|
||||||
|
progressHubProxy.value = getHubProxyFactory("IProgressHub").createHubProxy(
|
||||||
|
progressHubConnection.value,
|
||||||
|
);
|
||||||
|
getReceiverRegister("IProgressReceiver").register(
|
||||||
|
progressHubConnection.value,
|
||||||
|
progressHubReceiver,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileInput = useTemplateRef("fileInput");
|
||||||
|
const bitstream = defineModel("bitstreamFile", {
|
||||||
|
type: File,
|
||||||
|
default: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化时加载示例比特流
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!isUndefined(bitstream.value) && !isNull(fileInput.value)) {
|
||||||
let fileList = new DataTransfer();
|
let fileList = new DataTransfer();
|
||||||
fileList.items.add(bitstream.value);
|
fileList.items.add(bitstream.value);
|
||||||
fileInput.value.files = fileList.files;
|
fileInput.value.files = fileList.files;
|
||||||
}
|
}
|
||||||
|
|
||||||
await loadAvailableBitstreams();
|
await loadAvailableBitstreams();
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleFileChange(event: Event): void {
|
// 加载可用的比特流文件列表
|
||||||
const target = event.target as HTMLInputElement;
|
|
||||||
const file = target.files?.[0];
|
|
||||||
bitstream.value = file || undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkFileInput(): boolean {
|
|
||||||
if (!bitstream.value) {
|
|
||||||
dialog.error(`未选择文件`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const maxBytes = props.maxMemory! * 1024 * 1024;
|
|
||||||
if (bitstream.value.size > maxBytes) {
|
|
||||||
dialog.error(`文件大小超过最大限制: ${props.maxMemory}MB`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function downloadBitstream() {
|
|
||||||
currentTask.value = "programming";
|
|
||||||
try {
|
|
||||||
currentProgressId.value = await eqps.jtagDownloadBitstream(
|
|
||||||
currentBitstreamId.value,
|
|
||||||
);
|
|
||||||
progressTracker.register(
|
|
||||||
currentProgressId.value,
|
|
||||||
"programBitstream",
|
|
||||||
handleProgressUpdate,
|
|
||||||
);
|
|
||||||
} catch {
|
|
||||||
dialog.error("比特流烧录失败");
|
|
||||||
cleanProgressTracker();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanProgressTracker() {
|
|
||||||
currentTask.value = "none";
|
|
||||||
currentProgressId.value = "";
|
|
||||||
currentBitstreamId.value = "";
|
|
||||||
currentProgressPercent.value = 0;
|
|
||||||
progressTracker.unregister(currentProgressId.value, "programBitstream");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadAvailableBitstreams() {
|
async function loadAvailableBitstreams() {
|
||||||
|
console.log("加载可用比特流文件,examId:", props.examId);
|
||||||
if (!props.examId) {
|
if (!props.examId) {
|
||||||
availableBitstreams.value = [];
|
availableBitstreams.value = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resourceClient = AuthManager.createClient(ResourceClient);
|
const resourceClient = AuthManager.createClient(ResourceClient);
|
||||||
|
// 使用新的ResourceClient API获取比特流模板资源列表
|
||||||
const resources = await resourceClient.getResourceList(
|
const resources = await resourceClient.getResourceList(
|
||||||
props.examId,
|
props.examId,
|
||||||
"bitstream",
|
"bitstream",
|
||||||
|
@ -201,82 +185,129 @@ async function loadAvailableBitstreams() {
|
||||||
availableBitstreams.value =
|
availableBitstreams.value =
|
||||||
resources.map((r) => ({ id: r.id, name: r.name })) || [];
|
resources.map((r) => ({ id: r.id, name: r.name })) || [];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error("加载比特流列表失败:", error);
|
||||||
availableBitstreams.value = [];
|
availableBitstreams.value = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 统一处理示例比特流的下载/烧录
|
// 下载示例比特流
|
||||||
async function handleExampleBitstream(
|
async function downloadExampleBitstream(bitstream: {
|
||||||
action: "download" | "program",
|
id: string;
|
||||||
bitstreamObj: { id: string; name: string },
|
name: string;
|
||||||
) {
|
}) {
|
||||||
if (currentTask.value !== "none") return;
|
if (isDownloading.value) return;
|
||||||
currentBitstreamId.value = bitstreamObj.id;
|
|
||||||
if (action === "download") {
|
isDownloading.value = true;
|
||||||
currentTask.value = "downloading";
|
try {
|
||||||
try {
|
const resourceClient = AuthManager.createClient(ResourceClient);
|
||||||
const resourceClient = AuthManager.createClient(ResourceClient);
|
|
||||||
const response = await resourceClient.getResourceById(bitstreamObj.id);
|
// 使用新的ResourceClient API获取资源文件
|
||||||
if (response && response.data) {
|
const response = await resourceClient.getResourceById(bitstream.id);
|
||||||
const url = URL.createObjectURL(response.data);
|
|
||||||
const link = document.createElement("a");
|
if (response && response.data) {
|
||||||
link.href = url;
|
// 创建下载链接
|
||||||
link.download = response.fileName || bitstreamObj.name;
|
const url = URL.createObjectURL(response.data);
|
||||||
document.body.appendChild(link);
|
const link = document.createElement("a");
|
||||||
link.click();
|
link.href = url;
|
||||||
document.body.removeChild(link);
|
link.download = response.fileName || bitstream.name;
|
||||||
URL.revokeObjectURL(url);
|
document.body.appendChild(link);
|
||||||
alert.info("示例比特流下载成功");
|
link.click();
|
||||||
} else {
|
document.body.removeChild(link);
|
||||||
alert.error("下载失败:响应数据为空");
|
URL.revokeObjectURL(url);
|
||||||
}
|
|
||||||
} catch {
|
dialog.info("示例比特流下载成功");
|
||||||
alert.error("下载示例比特流失败");
|
} else {
|
||||||
} finally {
|
dialog.error("下载失败:响应数据为空");
|
||||||
currentTask.value = "none";
|
|
||||||
currentBitstreamId.value = "";
|
|
||||||
}
|
}
|
||||||
} else if (action === "program") {
|
} catch (error) {
|
||||||
currentBitstreamId.value = bitstreamObj.id;
|
console.error("下载示例比特流失败:", error);
|
||||||
await downloadBitstream();
|
dialog.error("下载示例比特流失败");
|
||||||
|
} finally {
|
||||||
|
isDownloading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上传并下载
|
// 直接烧录示例比特流
|
||||||
async function handleUploadAndDownload() {
|
async function programExampleBitstream(bitstream: {
|
||||||
if (currentTask.value !== "none") return;
|
id: string;
|
||||||
if (!checkFileInput()) return;
|
name: string;
|
||||||
|
}) {
|
||||||
|
if (isProgramming.value) return;
|
||||||
|
|
||||||
currentTask.value = "uploading";
|
isProgramming.value = true;
|
||||||
let uploadedBitstreamId: string | null = null;
|
|
||||||
try {
|
try {
|
||||||
uploadedBitstreamId = await eqps.jtagUploadBitstream(
|
const downloadTaskId = await eqps.jtagDownloadBitstream(bitstream.id);
|
||||||
bitstream.value!,
|
} catch (error) {
|
||||||
props.examId || "",
|
console.error("烧录示例比特流失败:", error);
|
||||||
);
|
dialog.error("烧录示例比特流失败");
|
||||||
if (!uploadedBitstreamId) throw new Error("上传失败");
|
} finally {
|
||||||
emits("finishedUpload", bitstream.value!);
|
isProgramming.value = false;
|
||||||
} catch {
|
}
|
||||||
dialog.error("上传失败");
|
}
|
||||||
currentTask.value = "none";
|
|
||||||
|
function handleFileChange(event: Event): void {
|
||||||
|
const target = event.target as HTMLInputElement;
|
||||||
|
const file = target.files?.[0]; // 获取选中的第一个文件
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentBitstreamId.value = uploadedBitstreamId;
|
bitstream.value = file;
|
||||||
|
|
||||||
await downloadBitstream();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleProgressUpdate(msg: ProgressInfo) {
|
function checkFile(file: File): boolean {
|
||||||
// console.log(msg);
|
const maxBytes = props.maxMemory! * 1024 * 1024; // 将最大容量从 MB 转换为字节
|
||||||
if (msg.status === ProgressStatus.Running)
|
if (file.size > maxBytes) {
|
||||||
currentProgressPercent.value = msg.progressPercent;
|
dialog.error(`文件大小超过最大限制: ${props.maxMemory}MB`);
|
||||||
else if (msg.status === ProgressStatus.Failed) {
|
return false;
|
||||||
dialog.error(`比特流烧录失败: ${msg.errorMessage}`);
|
}
|
||||||
cleanProgressTracker();
|
return true;
|
||||||
} else if (msg.status === ProgressStatus.Completed) {
|
}
|
||||||
dialog.info("比特流烧录成功");
|
|
||||||
cleanProgressTracker();
|
async function handleClick(event: Event): Promise<void> {
|
||||||
|
console.log("上传按钮被点击");
|
||||||
|
if (isNull(bitstream.value) || isUndefined(bitstream.value)) {
|
||||||
|
dialog.error(`未选择文件`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!checkFile(bitstream.value)) return;
|
||||||
|
|
||||||
|
isUploading.value = true;
|
||||||
|
let uploadedBitstreamId: string | null = null;
|
||||||
|
try {
|
||||||
|
console.log("开始上传比特流文件:", bitstream.value.name);
|
||||||
|
const bitstreamId = await eqps.jtagUploadBitstream(
|
||||||
|
bitstream.value,
|
||||||
|
props.examId || "",
|
||||||
|
);
|
||||||
|
console.log("上传结果,ID:", bitstreamId);
|
||||||
|
if (bitstreamId === null || bitstreamId === undefined) {
|
||||||
|
isUploading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uploadedBitstreamId = bitstreamId;
|
||||||
|
} catch (e) {
|
||||||
|
dialog.error("上传失败");
|
||||||
|
console.error(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isUploading.value = false;
|
||||||
|
|
||||||
|
// Download
|
||||||
|
try {
|
||||||
|
console.log("开始下载比特流,ID:", uploadedBitstreamId);
|
||||||
|
if (uploadedBitstreamId === null || uploadedBitstreamId === undefined) {
|
||||||
|
dialog.error("uploadedBitstreamId is null or undefined");
|
||||||
|
} else {
|
||||||
|
isDownloading.value = true;
|
||||||
|
downloadTaskId.value =
|
||||||
|
await eqps.jtagDownloadBitstream(uploadedBitstreamId);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
dialog.error("下载失败");
|
||||||
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -127,10 +127,6 @@ const switchCount = computed(() => {
|
||||||
else return props.switchCount;
|
else return props.switchCount;
|
||||||
});
|
});
|
||||||
|
|
||||||
function getClient() {
|
|
||||||
return AuthManager.createClient(SwitchClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析初始值
|
// 解析初始值
|
||||||
function parseInitialValues(): boolean[] {
|
function parseInitialValues(): boolean[] {
|
||||||
if (Array.isArray(props.initialValues)) {
|
if (Array.isArray(props.initialValues)) {
|
||||||
|
@ -166,7 +162,7 @@ function updateStatus(newStates: boolean[], index?: number) {
|
||||||
btnStatus.value = newStates.slice(0, switchCount.value);
|
btnStatus.value = newStates.slice(0, switchCount.value);
|
||||||
if (props.enableDigitalTwin) {
|
if (props.enableDigitalTwin) {
|
||||||
try {
|
try {
|
||||||
const client = getClient();
|
const client = AuthManager.createClient(SwitchClient);
|
||||||
if (!isUndefined(index)) client.setSwitchOnOff(index, newStates[index]);
|
if (!isUndefined(index)) client.setSwitchOnOff(index, newStates[index]);
|
||||||
else client.setMultiSwitchsOnOff(btnStatus.value);
|
else client.setMultiSwitchsOnOff(btnStatus.value);
|
||||||
} catch (error: any) {}
|
} catch (error: any) {}
|
||||||
|
@ -190,15 +186,6 @@ function setBtnStatus(idx: number, isOn: boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听 props 变化只同步一次
|
// 监听 props 变化只同步一次
|
||||||
watch(
|
|
||||||
() => props.enableDigitalTwin,
|
|
||||||
(newVal) => {
|
|
||||||
const client = getClient();
|
|
||||||
client.setEnable(newVal);
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => [switchCount.value, props.initialValues],
|
() => [switchCount.value, props.initialValues],
|
||||||
() => {
|
() => {
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
import type { HubConnection } from "@microsoft/signalr";
|
|
||||||
import type {
|
|
||||||
IProgressHub,
|
|
||||||
IProgressReceiver,
|
|
||||||
} from "@/utils/signalR/TypedSignalR.Client/server.Hubs";
|
|
||||||
import {
|
|
||||||
getHubProxyFactory,
|
|
||||||
getReceiverRegister,
|
|
||||||
} from "@/utils/signalR/TypedSignalR.Client";
|
|
||||||
import { ProgressStatus, type ProgressInfo } from "@/utils/signalR/server.Hubs";
|
|
||||||
import { onMounted, onUnmounted, ref, shallowRef } from "vue";
|
|
||||||
import { defineStore } from "pinia";
|
|
||||||
import { AuthManager } from "@/utils/AuthManager";
|
|
||||||
import { forEach, isUndefined } from "lodash";
|
|
||||||
|
|
||||||
export type ProgressCallback = (msg: ProgressInfo) => void;
|
|
||||||
|
|
||||||
export const useProgressStore = defineStore("progress", () => {
|
|
||||||
// taskId -> name -> callback
|
|
||||||
const progressCallbackFuncs = shallowRef<
|
|
||||||
Map<string, Map<string, ProgressCallback>>
|
|
||||||
>(new Map());
|
|
||||||
|
|
||||||
const progressHubConnection = shallowRef<HubConnection>();
|
|
||||||
const progressHubProxy = shallowRef<IProgressHub>();
|
|
||||||
const progressHubReceiver: IProgressReceiver = {
|
|
||||||
onReceiveProgress: async (msg) => {
|
|
||||||
const taskMap = progressCallbackFuncs.value.get(msg.taskId);
|
|
||||||
if (taskMap) {
|
|
||||||
for (const func of taskMap.values()) {
|
|
||||||
func(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
progressHubConnection.value =
|
|
||||||
AuthManager.createHubConnection("ProgressHub");
|
|
||||||
progressHubProxy.value = getHubProxyFactory("IProgressHub").createHubProxy(
|
|
||||||
progressHubConnection.value,
|
|
||||||
);
|
|
||||||
getReceiverRegister("IProgressReceiver").register(
|
|
||||||
progressHubConnection.value,
|
|
||||||
progressHubReceiver,
|
|
||||||
);
|
|
||||||
progressHubConnection.value.start();
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
if (progressHubConnection.value) {
|
|
||||||
progressHubConnection.value.stop();
|
|
||||||
progressHubConnection.value = undefined;
|
|
||||||
progressHubProxy.value = undefined;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function register(progressId: string, name: string, func: ProgressCallback) {
|
|
||||||
progressHubProxy.value?.join(progressId);
|
|
||||||
let taskMap = progressCallbackFuncs.value.get(progressId);
|
|
||||||
if (!taskMap) {
|
|
||||||
taskMap = new Map();
|
|
||||||
progressCallbackFuncs.value?.set(progressId, taskMap);
|
|
||||||
}
|
|
||||||
taskMap.set(name, func);
|
|
||||||
}
|
|
||||||
|
|
||||||
function unregister(taskId: string, name: string) {
|
|
||||||
progressHubProxy.value?.leave(taskId);
|
|
||||||
const taskMap = progressCallbackFuncs.value.get(taskId);
|
|
||||||
if (taskMap) {
|
|
||||||
taskMap.delete(name);
|
|
||||||
if (taskMap.size === 0) {
|
|
||||||
progressCallbackFuncs.value?.delete(taskId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
register,
|
|
||||||
unregister,
|
|
||||||
};
|
|
||||||
});
|
|
|
@ -161,14 +161,6 @@ class IProgressHub_HubProxy implements IProgressHub {
|
||||||
public readonly join = async (taskId: string): Promise<boolean> => {
|
public readonly join = async (taskId: string): Promise<boolean> => {
|
||||||
return await this.connection.invoke("Join", taskId);
|
return await this.connection.invoke("Join", taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly leave = async (taskId: string): Promise<boolean> => {
|
|
||||||
return await this.connection.invoke("Leave", taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly getProgress = async (taskId: string): Promise<ProgressInfo> => {
|
|
||||||
return await this.connection.invoke("GetProgress", taskId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -48,16 +48,6 @@ export type IProgressHub = {
|
||||||
* @returns Transpiled from System.Threading.Tasks.Task<bool>
|
* @returns Transpiled from System.Threading.Tasks.Task<bool>
|
||||||
*/
|
*/
|
||||||
join(taskId: string): Promise<boolean>;
|
join(taskId: string): Promise<boolean>;
|
||||||
/**
|
|
||||||
* @param taskId Transpiled from string
|
|
||||||
* @returns Transpiled from System.Threading.Tasks.Task<bool>
|
|
||||||
*/
|
|
||||||
leave(taskId: string): Promise<boolean>;
|
|
||||||
/**
|
|
||||||
* @param taskId Transpiled from string
|
|
||||||
* @returns Transpiled from System.Threading.Tasks.Task<server.Hubs.ProgressInfo?>
|
|
||||||
*/
|
|
||||||
getProgress(taskId: string): Promise<ProgressInfo>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IDigitalTubesReceiver = {
|
export type IDigitalTubesReceiver = {
|
||||||
|
|
|
@ -12,10 +12,11 @@ export type DigitalTubeTaskStatus = {
|
||||||
|
|
||||||
/** Transpiled from server.Hubs.ProgressStatus */
|
/** Transpiled from server.Hubs.ProgressStatus */
|
||||||
export enum ProgressStatus {
|
export enum ProgressStatus {
|
||||||
Running = 0,
|
Pending = 0,
|
||||||
Completed = 1,
|
InProgress = 1,
|
||||||
Canceled = 2,
|
Completed = 2,
|
||||||
Failed = 3,
|
Canceled = 3,
|
||||||
|
Failed = 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Transpiled from server.Hubs.ProgressInfo */
|
/** Transpiled from server.Hubs.ProgressInfo */
|
||||||
|
|
Loading…
Reference in New Issue