using Microsoft.AspNetCore.SignalR; namespace RobotNet10.FleetManager.Hubs; /// /// SignalR Hub for real-time robot state and visualization updates. /// Manages client subscriptions to robot groups for receiving VDA5050 messages. /// /// /// Clients subscribe to specific robots using SubscribeToRobot method. /// Messages are broadcast to groups named "robot:{robotId}". /// public class RobotStateHub( Services.Logger logger, RobotStateHubContext hubContext) : Hub { private readonly Services.Logger _logger = logger; private readonly RobotStateHubContext _hubContext = hubContext; /// /// Subscribe to receive updates for a specific robot /// /// Robot identifier (serialNumber) public async Task SubscribeToRobot(string robotId) { var groupName = GetGroupName(robotId); await Groups.AddToGroupAsync(Context.ConnectionId, groupName); } /// /// Unsubscribe from updates for a specific robot /// /// Robot identifier (serialNumber) public async Task UnsubscribeFromRobot(string robotId) { var groupName = GetGroupName(robotId); await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName); } private static string GetGroupName(string robotId) { return $"robot:{robotId}"; } /// /// Subscribe to receive monitor updates for a specific levelId /// Each connection can only subscribe to one levelId at a time. /// Maximum 5 connections per levelId (FIFO). /// /// Level identifier (LayoutLevel.Id) public async Task SubscribeToLevelForMonitor(Guid levelId) { var evictedConnectionId = _hubContext.SubscribeToLevel(Context.ConnectionId, levelId); // If a connection was evicted, notify it if (evictedConnectionId != null) { _logger.Info($"Connection {evictedConnectionId} evicted from level {levelId} (max 5 connections reached)"); await Clients.Client(evictedConnectionId).SendAsync("OnMonitorDeactivated"); } await Task.CompletedTask; } /// /// Unsubscribe from monitor updates for the current level /// public async Task UnsubscribeFromLevelForMonitor() { var removed = _hubContext.UnsubscribeFromLevel(Context.ConnectionId); if (removed) { _logger.Info($"Client {Context.ConnectionId} unsubscribed from level for monitor"); } await Task.CompletedTask; } public override async Task OnDisconnectedAsync(Exception? exception) { // Remove connection from subscription manager _hubContext.RemoveConnection(Context.ConnectionId); if (exception != null) { _logger.Warning($"Client {Context.ConnectionId} disconnected with error: {exception.Message}"); } await base.OnDisconnectedAsync(exception); } }