feat: 完成七段数码管后端

This commit is contained in:
SikongJueluo 2025-08-14 15:21:15 +08:00
parent 0e07a5996a
commit 7bfc362b1f
No known key found for this signature in database
13 changed files with 189 additions and 58 deletions

View File

@ -6,7 +6,7 @@ using server.Services;
public class ProgressTrackerTest public class ProgressTrackerTest
{ {
[Fact] [Fact]
public async Task Test_ProgressReporter_Basic() public void Test_ProgressReporter_Basic()
{ {
int reportedValue = -1; int reportedValue = -1;
var reporter = new ProgressReporter(async v => { reportedValue = v; await Task.CompletedTask; }, 0, 100, 10); var reporter = new ProgressReporter(async v => { reportedValue = v; await Task.CompletedTask; }, 0, 100, 10);

View File

@ -31,7 +31,7 @@
<PackageReference Include="NSwag.CodeGeneration.TypeScript" Version="14.4.0" /> <PackageReference Include="NSwag.CodeGeneration.TypeScript" Version="14.4.0" />
<PackageReference Include="OpenCvSharp4" Version="4.11.0.20250507" /> <PackageReference Include="OpenCvSharp4" Version="4.11.0.20250507" />
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.11.0.20250507" /> <PackageReference Include="OpenCvSharp4.runtime.win" Version="4.11.0.20250507" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" /> <PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
<PackageReference Include="Tapper.Analyzer" Version="1.13.1"> <PackageReference Include="Tapper.Analyzer" Version="1.13.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@ -21,8 +21,8 @@ public class NetConfigController : ControllerBase
private const int BOARD_PORT = 1234; private const int BOARD_PORT = 1234;
// 本机网络信息 // 本机网络信息
private readonly IPAddress _localIP; private readonly IPAddress _localIP = IPAddress.Any;
private readonly byte[] _localMAC; private readonly byte[] _localMAC = new byte[6];
private readonly string _localIPString; private readonly string _localIPString;
private readonly string _localMACString; private readonly string _localMACString;
private readonly string _localInterface; private readonly string _localInterface;

View File

@ -0,0 +1,41 @@
using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.Cors;
using TypedSignalR.Client;
using Tapper;
using server.Services;
namespace server.Hubs;
[Hub]
public interface IDigitalTubesHub
{
Task<bool> Join(string taskId);
}
[Receiver]
public interface IDigitalTubesReceiver
{
Task OnReceive();
}
[Authorize]
[EnableCors("SignalR")]
public class DigitalTubesHub : Hub<IDigitalTubesReceiver>, IDigitalTubesHub
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private readonly IHubContext<DigitalTubesHub, IDigitalTubesReceiver> _hubContext;
public DigitalTubesHub(IHubContext<DigitalTubesHub, IDigitalTubesReceiver> hubContext)
{
_hubContext = hubContext;
}
public async Task<bool> Join(string taskId)
{
return true;
}
}

View File

@ -69,23 +69,26 @@ public class JtagHub : Hub<IJtagReceiver>, IJtagHub
public async Task<bool> SetBoundaryScanFreq(int freq) public async Task<bool> SetBoundaryScanFreq(int freq)
{ {
try return await Task.Run(() =>
{ {
var userName = Context.User?.FindFirstValue(ClaimTypes.Name); try
if (userName is null)
{ {
logger.Error("Can't get user info"); 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; 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) public async Task<bool> StartBoundaryScan(int freq = 100)
@ -99,7 +102,7 @@ public class JtagHub : Hub<IJtagReceiver>, IJtagHub
return false; return false;
} }
SetBoundaryScanFreq(freq); await SetBoundaryScanFreq(freq);
var cts = new CancellationTokenSource(); var cts = new CancellationTokenSource();
CancellationTokenSourceTable.AddOrUpdate(userName, cts, (key, value) => cts); CancellationTokenSourceTable.AddOrUpdate(userName, cts, (key, value) => cts);
@ -145,23 +148,27 @@ public class JtagHub : Hub<IJtagReceiver>, IJtagHub
public async Task<bool> StopBoundaryScan() public async Task<bool> StopBoundaryScan()
{ {
var userName = Context.User?.FindFirstValue(ClaimTypes.Name); return await Task.Run(() =>
if (userName is null)
{ {
logger.Error("No Such User"); var userName = Context.User?.FindFirstValue(ClaimTypes.Name);
return false; if (userName is null)
} {
logger.Error("No Such User");
return false;
}
if (!CancellationTokenSourceTable.TryGetValue(userName, out var cts)) if (!CancellationTokenSourceTable.TryGetValue(userName, out var cts))
{ {
return false; return false;
} }
cts.Cancel(); cts.Cancel();
cts.Token.WaitHandle.WaitOne(); cts.Token.WaitHandle.WaitOne();
logger.Info($"Boundary scan stopped for user {userName}");
return true;
});
logger.Info($"Boundary scan stopped for user {userName}");
return true;
} }
private async Task BoundaryScanLogicPorts(string connectionID, string userName, CancellationToken cancellationToken) private async Task BoundaryScanLogicPorts(string connectionID, string userName, CancellationToken cancellationToken)

View File

@ -33,10 +33,10 @@ public enum ProgressStatus
[TranspilationSource] [TranspilationSource]
public class ProgressInfo public class ProgressInfo
{ {
public string TaskId { get; } public virtual string TaskId { get; } = string.Empty;
public ProgressStatus Status { get; } public virtual ProgressStatus Status { get; }
public int ProgressPercent { get; } public virtual int ProgressPercent { get; } = 0;
public string ErrorMessage { get; } public virtual string ErrorMessage { get; } = string.Empty;
}; };
[Authorize] [Authorize]
@ -56,6 +56,6 @@ public class ProgressHub : Hub<IProgressReceiver>, IProgressHub
public async Task<bool> Join(string taskId) public async Task<bool> Join(string taskId)
{ {
return _tracker.BindTask(taskId, Context.ConnectionId); return await Task.Run(() => _tracker.BindTask(taskId, Context.ConnectionId));
} }
} }

View File

@ -21,7 +21,7 @@ public static class MsgBus
/// 通信总线初始化 /// 通信总线初始化
/// </summary> /// </summary>
/// <returns>无</returns> /// <returns>无</returns>
public async static void Init() public static void Init()
{ {
if (!ArpClient.IsAdministrator()) if (!ArpClient.IsAdministrator())
{ {

View File

@ -1,6 +1,5 @@
using System.Net; using System.Net;
using DotNext; using DotNext;
using Peripherals.PowerClient;
using WebProtocol; using WebProtocol;
namespace Peripherals.HdmiInClient; namespace Peripherals.HdmiInClient;

View File

@ -709,8 +709,10 @@ public class Jtag
/// 下载比特流到 JTAG 设备 /// 下载比特流到 JTAG 设备
/// </summary> /// </summary>
/// <param name="bitstream">比特流数据</param> /// <param name="bitstream">比特流数据</param>
/// <param name="progress">进度报告器</param>
/// <returns>指示下载是否成功的异步结果</returns> /// <returns>指示下载是否成功的异步结果</returns>
public async ValueTask<Result<bool>> DownloadBitstream(byte[] bitstream, ProgressReporter? progress = null) public async ValueTask<Result<bool>> DownloadBitstream(
byte[] bitstream, ProgressReporter? progress = null)
{ {
// Clear Data // Clear Data
MsgBus.UDPServer.ClearUDPData(this.address, 0); MsgBus.UDPServer.ClearUDPData(this.address, 0);

View File

@ -0,0 +1,79 @@
using System.Net;
using DotNext;
using Common;
namespace Peripherals.SevenDigitalTubesClient;
static class SevenDigitalTubesAddr
{
public const UInt32 BASE = 0x0000_0000;
}
public class SevenDigitalTubesCtrl
{
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
readonly int timeout = 500;
readonly int taskID;
readonly int port;
readonly string address;
private IPEndPoint ep;
/// <summary>
/// 初始化七段数码管控制器
/// </summary>
/// <param name="address">七段数码管控制器IP地址</param>
/// <param name="port">七段数码管控制器端口</param>
/// <param name="taskID">任务ID</param>
/// <param name="timeout">超时时间(毫秒)</param>
public SevenDigitalTubesCtrl(string address, int port, int taskID, int timeout = 500)
{
if (timeout < 0)
throw new ArgumentException("Timeout couldn't be negative", nameof(timeout));
this.address = address;
this.port = port;
this.ep = new IPEndPoint(IPAddress.Parse(address), port);
this.taskID = taskID;
this.timeout = timeout;
}
public async ValueTask<Result<byte>> ReadTube(int num)
{
if (num < 0 || num > 31)
throw new ArgumentOutOfRangeException(nameof(num), "Tube number must be between 0 and 31");
var ret = await UDPClientPool.ReadAddr(
this.ep, this.taskID, SevenDigitalTubesAddr.BASE + (UInt32)num, this.timeout);
if (!ret.IsSuccessful)
{
logger.Error($"Read tubes failed: {ret.Error}");
return new(ret.Error);
}
if (ret.Value.Options.Data == null || ret.Value.Options.Data.Length < 4)
return new(new Exception("Data length is too short"));
var data = Number.BytesToUInt32(ret.Value.Options.Data, 0, 4, true).Value;
if ((data >> 8) != num)
{
logger.Error($"Read wrong tube number: {num} != {data >> 8}");
return new(new Exception($"Read wrong tube number: {num} != {data >> 8}"));
}
return (byte)(data & 0xFF);
}
public async ValueTask<Result<byte[]>> ScanTubes()
{
var tubes = new byte[32];
for (int i = 0; i < 32; i++)
{
var ret = await ReadTube(i);
if (!ret.IsSuccessful)
return new(ret.Error);
tubes[i] = ret.Value;
}
return tubes;
}
}

View File

@ -192,11 +192,6 @@ public class HttpHdmiVideoStreamService : BackgroundService
} }
var hdmiInToken = _clientDict[boardId].CTS.Token; var hdmiInToken = _clientDict[boardId].CTS.Token;
if (hdmiInToken == null)
{
await SendErrorAsync(context.Response, "HDMI input is not available");
return;
}
if (path == "/snapshot") if (path == "/snapshot")
{ {

View File

@ -39,12 +39,12 @@ public class ProgressReporter : ProgressInfo, IProgress<int>
public ProgressReporter? Child { get; set; } public ProgressReporter? Child { get; set; }
private ProgressStatus _status = ProgressStatus.Pending; private ProgressStatus _status = ProgressStatus.Pending;
private string _errorMessage; private string _errorMessage = string.Empty;
public string TaskId { get; set; } = Guid.NewGuid().ToString(); public override string TaskId { get; } = Guid.NewGuid().ToString();
public int ProgressPercent => _progress * 100 / MaxProgress; public override int ProgressPercent => _progress * 100 / MaxProgress;
public ProgressStatus Status => _status; public override ProgressStatus Status => _status;
public string ErrorMessage => _errorMessage; public override string ErrorMessage => _errorMessage;
public ProgressReporter(Func<int, Task>? reporter = null, int initProgress = 0, int maxProgress = 100, int step = 1) public ProgressReporter(Func<int, Task>? reporter = null, int initProgress = 0, int maxProgress = 100, int step = 1)
{ {
@ -86,7 +86,7 @@ public class ProgressReporter : ProgressInfo, IProgress<int>
} }
} }
public async void Report(int value) public void Report(int value)
{ {
if (this._status == ProgressStatus.Pending) if (this._status == ProgressStatus.Pending)
this._status = ProgressStatus.InProgress; this._status = ProgressStatus.InProgress;
@ -153,7 +153,7 @@ public class ProgressTrackerService : BackgroundService
private class TaskProgressInfo private class TaskProgressInfo
{ {
public ProgressReporter Reporter { get; set; } public ProgressReporter? Reporter { get; set; }
public string? ConnectionId { get; set; } public string? ConnectionId { get; set; }
public required CancellationToken CancellationToken { get; set; } public required CancellationToken CancellationToken { get; set; }
public required CancellationTokenSource CancellationTokenSource { get; set; } public required CancellationTokenSource CancellationTokenSource { get; set; }
@ -176,10 +176,15 @@ public class ProgressTrackerService : BackgroundService
{ {
var info = kvp.Value; var info = kvp.Value;
// 超过 1 分钟且任务已完成/失败/取消 // 超过 1 分钟且任务已完成/失败/取消
if ((now - info.UpdatedAt).TotalMinutes > 1 && if (
(info.Reporter.Status == ProgressStatus.Completed || (now - info.UpdatedAt).TotalMinutes > 1 &&
info.Reporter.Status == ProgressStatus.Failed || info.Reporter != null &&
info.Reporter.Status == ProgressStatus.Canceled)) (
info.Reporter.Status == ProgressStatus.Completed ||
info.Reporter.Status == ProgressStatus.Failed ||
info.Reporter.Status == ProgressStatus.Canceled
)
)
{ {
_taskMap.TryRemove(kvp.Key, out _); _taskMap.TryRemove(kvp.Key, out _);
logger.Info($"Cleaned up task {kvp.Key}"); logger.Info($"Cleaned up task {kvp.Key}");
@ -219,7 +224,7 @@ public class ProgressTrackerService : BackgroundService
cancellationTokenSource.Token.ThrowIfCancellationRequested(); cancellationTokenSource.Token.ThrowIfCancellationRequested();
// 通过 SignalR 推送进度 // 通过 SignalR 推送进度
if (progressInfo.ConnectionId != null) if (progressInfo.ConnectionId != null && progressInfo.Reporter != null)
await _hubContext.Clients.Client(progressInfo.ConnectionId).OnReceiveProgress(progressInfo.Reporter); await _hubContext.Clients.Client(progressInfo.ConnectionId).OnReceiveProgress(progressInfo.Reporter);
}); });
@ -243,7 +248,8 @@ public class ProgressTrackerService : BackgroundService
{ {
if (_taskMap.TryGetValue(taskId, out var info)) if (_taskMap.TryGetValue(taskId, out var info))
{ {
return info.Reporter.Status; if (info.Reporter != null)
return info.Reporter.Status;
} }
return Optional<ProgressStatus>.None; return Optional<ProgressStatus>.None;
} }
@ -265,7 +271,7 @@ public class ProgressTrackerService : BackgroundService
{ {
try try
{ {
if (_taskMap.TryGetValue(taskId, out var info) && info != null) if (_taskMap.TryGetValue(taskId, out var info) && info != null && info.Reporter != null)
{ {
lock (info) lock (info)
{ {

View File

@ -607,6 +607,7 @@ public 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="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,
@ -654,6 +655,7 @@ public 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="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,