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 StartScan(); Task StopScan(); Task SetFrequency(int frequency); Task 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, IDigitalTubesHub { private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); private readonly IHubContext _hubContext; private readonly Database.UserManager _userManager = new(); private ConcurrentDictionary<(string, string), ScanTaskInfo> _scanTasks = new(); public DigitalTubesHub(IHubContext hubContext) { _hubContext = hubContext; } private Optional 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 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 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 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 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); } } }