258 lines
7.5 KiB
C#
258 lines
7.5 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
using System.Security.Claims;
|
|
using Microsoft.AspNetCore.SignalR;
|
|
using Microsoft.AspNetCore.Cors;
|
|
using TypedSignalR.Client;
|
|
using Tapper;
|
|
using DotNext;
|
|
using Peripherals.SevenDigitalTubesClient;
|
|
using System.Collections.Concurrent;
|
|
|
|
#pragma warning disable 1998
|
|
|
|
namespace server.Hubs;
|
|
|
|
[Hub]
|
|
public interface IDigitalTubesHub
|
|
{
|
|
Task<bool> StartScan();
|
|
Task<bool> StopScan();
|
|
Task<bool> SetFrequency(int frequency);
|
|
Task<DigitalTubeTaskStatus?> GetStatus();
|
|
}
|
|
|
|
[Receiver]
|
|
public interface IDigitalTubesReceiver
|
|
{
|
|
Task OnReceive(byte[] data);
|
|
}
|
|
|
|
[TranspilationSource]
|
|
public class DigitalTubeTaskStatus
|
|
{
|
|
public int Frequency { get; set; } = 100;
|
|
public bool IsRunning { get; set; } = false;
|
|
|
|
public DigitalTubeTaskStatus(ScanTaskInfo info)
|
|
{
|
|
Frequency = info.Frequency;
|
|
IsRunning = info.IsRunning;
|
|
}
|
|
}
|
|
|
|
public class ScanTaskInfo
|
|
{
|
|
public string BoardID { get; set; }
|
|
public string ClientID { get; set; }
|
|
public Task? ScanTask { get; set; }
|
|
public SevenDigitalTubesCtrl TubeClient { get; set; }
|
|
public CancellationTokenSource CTS { get; set; } = new();
|
|
public int Frequency { get; set; } = 100;
|
|
public bool IsRunning { get; set; } = false;
|
|
|
|
public ScanTaskInfo(
|
|
string boardID, string clientID, SevenDigitalTubesCtrl client)
|
|
{
|
|
BoardID = boardID;
|
|
ClientID = clientID;
|
|
TubeClient = client;
|
|
}
|
|
}
|
|
|
|
[Authorize]
|
|
[EnableCors("SignalR")]
|
|
public class DigitalTubesHub : Hub<IDigitalTubesReceiver>, IDigitalTubesHub
|
|
{
|
|
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
|
private readonly IHubContext<DigitalTubesHub, IDigitalTubesReceiver> _hubContext;
|
|
private readonly Database.UserManager _userManager = new();
|
|
|
|
private ConcurrentDictionary<(string, string), ScanTaskInfo> _scanTasks = new();
|
|
|
|
public DigitalTubesHub(IHubContext<DigitalTubesHub, IDigitalTubesReceiver> hubContext)
|
|
{
|
|
_hubContext = hubContext;
|
|
}
|
|
|
|
private Optional<Database.Board> TryGetBoard()
|
|
{
|
|
var userName = Context.User?.FindFirstValue(ClaimTypes.Name);
|
|
if (string.IsNullOrEmpty(userName))
|
|
{
|
|
logger.Error("User name is null or empty");
|
|
return null;
|
|
}
|
|
|
|
var userRet = _userManager.GetUserByName(userName);
|
|
if (!userRet.IsSuccessful || !userRet.Value.HasValue)
|
|
{
|
|
logger.Error($"User '{userName}' not found");
|
|
return null;
|
|
}
|
|
var user = userRet.Value.Value;
|
|
|
|
var boardRet = _userManager.GetBoardByID(user.BoardID);
|
|
if (!boardRet.IsSuccessful || !boardRet.Value.HasValue)
|
|
{
|
|
logger.Error($"Board not found");
|
|
return null;
|
|
}
|
|
return boardRet.Value.Value;
|
|
}
|
|
|
|
private Task ScanAllTubes(ScanTaskInfo scanInfo)
|
|
{
|
|
var token = scanInfo.CTS.Token;
|
|
return Task.Run(async () =>
|
|
{
|
|
var cntError = 0;
|
|
while (!token.IsCancellationRequested)
|
|
{
|
|
var beginTime = DateTime.Now;
|
|
var waitTime = TimeSpan.FromMilliseconds(1000 / scanInfo.Frequency);
|
|
|
|
var dataRet = await scanInfo.TubeClient.ScanAllTubes();
|
|
if (!dataRet.IsSuccessful)
|
|
{
|
|
logger.Error($"Failed to scan tubes: {dataRet.Error}");
|
|
cntError++;
|
|
if (cntError > 3)
|
|
{
|
|
logger.Error($"Too many errors, stopping scan");
|
|
break;
|
|
}
|
|
}
|
|
await _hubContext.Clients.Client(scanInfo.ClientID).OnReceive(dataRet.Value);
|
|
|
|
var processTime = DateTime.Now - beginTime;
|
|
if (processTime < waitTime)
|
|
{
|
|
await Task.Delay(waitTime - processTime, token);
|
|
}
|
|
}
|
|
scanInfo.IsRunning = false;
|
|
}, token)
|
|
.ContinueWith((task) =>
|
|
{
|
|
if (task.IsFaulted)
|
|
{
|
|
logger.Error(
|
|
$"Digital tubes scan operation failesj for board {task.Exception}");
|
|
}
|
|
else if (task.IsCanceled)
|
|
{
|
|
logger.Info(
|
|
$"Digital tubes scan operation cancelled for board {scanInfo.BoardID}");
|
|
}
|
|
else
|
|
{
|
|
logger.Info(
|
|
$"Digital tubes scan completed successfully for board {scanInfo.BoardID}");
|
|
}
|
|
});
|
|
}
|
|
|
|
public async Task<bool> StartScan()
|
|
{
|
|
try
|
|
{
|
|
var board = TryGetBoard().OrThrow(() => new Exception("Board not found"));
|
|
var key = (board.ID.ToString(), Context.ConnectionId);
|
|
|
|
if (_scanTasks.TryGetValue(key, out var existing) && existing.IsRunning)
|
|
return true;
|
|
|
|
var cts = new CancellationTokenSource();
|
|
var scanTaskInfo = new ScanTaskInfo(
|
|
board.ID.ToString(), Context.ConnectionId,
|
|
new SevenDigitalTubesCtrl(board.IpAddr, board.Port, 0)
|
|
);
|
|
scanTaskInfo.ScanTask = ScanAllTubes(scanTaskInfo);
|
|
|
|
_scanTasks[key] = scanTaskInfo;
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.Error(ex, "Failed to start scan");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public async Task<bool> StopScan()
|
|
{
|
|
try
|
|
{
|
|
var board = TryGetBoard().OrThrow(() => new Exception("Board not found"));
|
|
var key = (board.ID.ToString(), Context.ConnectionId);
|
|
|
|
if (_scanTasks.TryRemove(key, out var scanInfo))
|
|
{
|
|
scanInfo.IsRunning = false;
|
|
scanInfo.CTS.Cancel();
|
|
if (scanInfo.ScanTask != null)
|
|
await scanInfo.ScanTask;
|
|
scanInfo.CTS.Dispose();
|
|
}
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.Error(ex, "Failed to stop scan");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public async Task<bool> SetFrequency(int frequency)
|
|
{
|
|
try
|
|
{
|
|
if (frequency < 1 || frequency > 1000)
|
|
return false;
|
|
|
|
var board = TryGetBoard().OrThrow(() => new Exception("Board not found"));
|
|
var key = (board.ID.ToString(), Context.ConnectionId);
|
|
|
|
if (_scanTasks.TryGetValue(key, out var scanInfo) && scanInfo.IsRunning)
|
|
{
|
|
scanInfo.Frequency = frequency;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
logger.Warn($"SetFrequency called but no running scan for board {board.ID} and client {Context.ConnectionId}");
|
|
return false;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.Error(ex, "Failed to set frequency");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public async Task<DigitalTubeTaskStatus?> GetStatus()
|
|
{
|
|
try
|
|
{
|
|
var board = TryGetBoard().OrThrow(() => new Exception("Board not found"));
|
|
var key = (board.ID.ToString(), Context.ConnectionId);
|
|
|
|
if (_scanTasks.TryGetValue(key, out var scanInfo))
|
|
{
|
|
return new DigitalTubeTaskStatus(scanInfo);
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.Error(ex, "Failed to get status");
|
|
throw new Exception("Failed to get status", ex);
|
|
}
|
|
}
|
|
|
|
}
|