Merge branch 'master' of ssh://git.swordlost.top:222/SikongJueluo/FPGA_WebLab
This commit is contained in:
@@ -5,13 +5,13 @@ 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;
|
||||
using TypedSignalR.Client.DevTools;
|
||||
|
||||
// Early init of NLog to allow startup and exception logging, before host is built
|
||||
var logger = NLog.LogManager.Setup()
|
||||
@@ -97,6 +97,9 @@ try
|
||||
);
|
||||
});
|
||||
|
||||
// Use SignalR
|
||||
builder.Services.AddSignalR();
|
||||
|
||||
// Add Swagger
|
||||
builder.Services.AddSwaggerDocument(options =>
|
||||
{
|
||||
@@ -198,9 +201,12 @@ try
|
||||
};
|
||||
});
|
||||
app.UseSwaggerUi();
|
||||
app.UseSignalRHubSpecification();
|
||||
app.UseSignalRHubDevelopmentUI();
|
||||
|
||||
// Router
|
||||
app.MapControllers();
|
||||
app.MapHub<server.Hubs.JtagHub.JtagHub>("hubs/JtagHub").RequireCors("Users");
|
||||
|
||||
// Setup Program
|
||||
MsgBus.Init();
|
||||
@@ -231,7 +237,7 @@ try
|
||||
logger.Error(err);
|
||||
return Results.Problem(err.ToString());
|
||||
}
|
||||
});
|
||||
}).RequireCors("Development");
|
||||
|
||||
app.Run();
|
||||
}
|
||||
@@ -252,4 +258,3 @@ finally
|
||||
// Close Program
|
||||
MsgBus.Exit();
|
||||
}
|
||||
|
||||
|
@@ -32,6 +32,16 @@
|
||||
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.11.0.20250507" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
|
||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
|
||||
<PackageReference Include="Tapper.Analyzer" Version="1.13.1">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="TypedSignalR.Client.DevTools" Version="1.2.4" />
|
||||
<PackageReference Include="TypedSignalR.Client.TypeScript.Analyzer" Version="1.15.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="TypedSignalR.Client.TypeScript.Attributes" Version="1.15.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -133,7 +133,7 @@ public class Board
|
||||
/// </summary>
|
||||
public enum BoardStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// 未启用状态,无法被使用
|
||||
/// </summary>
|
||||
Disabled,
|
||||
@@ -687,6 +687,31 @@ public class AppDataConnection : DataConnection
|
||||
return new(boards[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据用户名获取实验板信息
|
||||
/// </summary>
|
||||
/// <param name="userName">用户名</param>
|
||||
/// <returns>包含实验板信息的结果,如果未找到则返回空</returns>
|
||||
public Result<Optional<Board>> GetBoardByUserName(string userName)
|
||||
{
|
||||
var boards = this.BoardTable.Where(board => board.OccupiedUserName == userName).ToArray();
|
||||
|
||||
if (boards.Length > 1)
|
||||
{
|
||||
logger.Error($"数据库中存在多个相同用户名的实验板: {userName}");
|
||||
return new(new Exception($"数据库中存在多个相同用户名的实验板: {userName}"));
|
||||
}
|
||||
|
||||
if (boards.Length == 0)
|
||||
{
|
||||
logger.Info($"未找到用户名对应的实验板: {userName}");
|
||||
return new(Optional<Board>.None);
|
||||
}
|
||||
|
||||
logger.Debug($"成功获取实验板信息: {userName}");
|
||||
return new(boards[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有实验板信息
|
||||
/// </summary>
|
||||
|
176
server/src/Hubs/JtagHub.cs
Normal file
176
server/src/Hubs/JtagHub.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using DotNext;
|
||||
using System.Collections.Concurrent;
|
||||
using TypedSignalR.Client;
|
||||
using Tapper;
|
||||
|
||||
namespace server.Hubs.JtagHub;
|
||||
|
||||
[Hub]
|
||||
public interface IJtagHub
|
||||
{
|
||||
Task<bool> SetBoundaryScanFreq(int freq);
|
||||
Task<bool> StartBoundaryScan(int freq = 100);
|
||||
Task<bool> StopBoundaryScan();
|
||||
}
|
||||
|
||||
[Receiver]
|
||||
public interface IJtagReceiver
|
||||
{
|
||||
Task OnReceiveBoundaryScanData(Dictionary<string, bool> msg);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
public class JtagHub : Hub<IJtagReceiver>, IJtagHub
|
||||
{
|
||||
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
private ConcurrentDictionary<string, int> FreqTable = new();
|
||||
private ConcurrentDictionary<string, CancellationTokenSource> CancellationTokenSourceTable = new();
|
||||
|
||||
private Optional<Peripherals.JtagClient.Jtag> GetJtagClient(string userName)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var db = new Database.AppDataConnection();
|
||||
var board = db.GetBoardByUserName(userName);
|
||||
if (!board.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Find Board {board.Value.Value.ID} failed because {board.Error}");
|
||||
return new(null);
|
||||
}
|
||||
if (!board.Value.HasValue)
|
||||
{
|
||||
logger.Error($"Board {board.Value.Value.ID} not found");
|
||||
return new(null);
|
||||
}
|
||||
|
||||
var jtag = new Peripherals.JtagClient.Jtag(board.Value.Value.IpAddr, board.Value.Value.Port);
|
||||
return new(jtag);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
logger.Error(error);
|
||||
return new(null);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> SetBoundaryScanFreq(int freq)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userName = Context.User?.FindFirstValue(ClaimTypes.Name);
|
||||
if (userName is null)
|
||||
{
|
||||
logger.Error("Can't get user info");
|
||||
return false;
|
||||
}
|
||||
|
||||
FreqTable.AddOrUpdate(userName, freq, (key, value) => freq);
|
||||
return true;
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
logger.Error(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> StartBoundaryScan(int freq = 100)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userName = Context.User?.FindFirstValue(ClaimTypes.Name);
|
||||
if (userName is null)
|
||||
{
|
||||
logger.Error("No Such User");
|
||||
return false;
|
||||
}
|
||||
|
||||
await SetBoundaryScanFreq(freq);
|
||||
var cts = CancellationTokenSource.CreateLinkedTokenSource();
|
||||
CancellationTokenSourceTable.AddOrUpdate(userName, cts, (key, value) => cts);
|
||||
|
||||
_ = Task
|
||||
.Run(
|
||||
() => BoundaryScanLogicPorts(
|
||||
Context.ConnectionId,
|
||||
userName,
|
||||
cts.Token),
|
||||
cts.Token)
|
||||
.ContinueWith((task) =>
|
||||
{
|
||||
if (!task.IsFaulted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (task.Exception.InnerException is OperationCanceledException)
|
||||
{
|
||||
logger.Info($"Boundary scan operation cancelled for user {userName}");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error(task.Exception);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
logger.Error(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> StopBoundaryScan()
|
||||
{
|
||||
var userName = Context.User?.FindFirstValue(ClaimTypes.Name);
|
||||
if (userName is null)
|
||||
{
|
||||
logger.Error("No Such User");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CancellationTokenSourceTable.TryGetValue(userName, out var cts))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
cts.Cancel();
|
||||
cts.Token.WaitHandle.WaitOne();
|
||||
return true;
|
||||
}
|
||||
|
||||
private async void BoundaryScanLogicPorts(string connectionID, string userName, CancellationToken cancellationToken)
|
||||
{
|
||||
var jtagCtrl = GetJtagClient(userName).OrThrow(() => new InvalidOperationException("JTAG client not found"));
|
||||
var cntFail = 0;
|
||||
|
||||
while (true && cntFail < 5)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var ret = await jtagCtrl.BoundaryScanLogicalPorts();
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"User {userName} boundary scan failed for device {jtagCtrl.address}: {ret.Error}");
|
||||
cntFail++;
|
||||
}
|
||||
|
||||
await this.Clients.Client(connectionID).OnReceiveBoundaryScanData(ret.Value);
|
||||
// logger.Info($"User {userName} successfully completed boundary scan for device {jtagCtrl.address}");
|
||||
|
||||
await Task.Delay(FreqTable.TryGetValue(userName, out var freq) ? 1000 / freq : 1000 / 100, cancellationToken);
|
||||
}
|
||||
|
||||
if (cntFail >= 5)
|
||||
{
|
||||
logger.Error($"User {userName} boundary scan failed for device {jtagCtrl.address} after 5 attempts");
|
||||
throw new InvalidOperationException("Boundary scan failed");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -386,7 +386,10 @@ public class Jtag
|
||||
readonly int timeout;
|
||||
|
||||
readonly int port;
|
||||
readonly string address;
|
||||
/// <summary>
|
||||
/// Jtag控制器IP地址
|
||||
/// </summary>
|
||||
public readonly string address;
|
||||
private IPEndPoint ep;
|
||||
|
||||
/// <summary>
|
||||
|
Reference in New Issue
Block a user