Initial commit

This commit is contained in:
2026-07-03 16:31:37 +07:00
commit 899c7c637d
1939 changed files with 641750 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
using Microsoft.AspNetCore.SignalR;
namespace RobotNet10.FleetManager.Hubs;
/// <summary>
/// SignalR Hub for real-time robot state and visualization updates.
/// Manages client subscriptions to robot groups for receiving VDA5050 messages.
/// </summary>
/// <remarks>
/// Clients subscribe to specific robots using SubscribeToRobot method.
/// Messages are broadcast to groups named "robot:{robotId}".
/// </remarks>
public class RobotStateHub(
Services.Logger<RobotStateHub> logger,
RobotStateHubContext hubContext) : Hub
{
private readonly Services.Logger<RobotStateHub> _logger = logger;
private readonly RobotStateHubContext _hubContext = hubContext;
/// <summary>
/// Subscribe to receive updates for a specific robot
/// </summary>
/// <param name="robotId">Robot identifier (serialNumber)</param>
public async Task SubscribeToRobot(string robotId)
{
var groupName = GetGroupName(robotId);
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
}
/// <summary>
/// Unsubscribe from updates for a specific robot
/// </summary>
/// <param name="robotId">Robot identifier (serialNumber)</param>
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}";
}
/// <summary>
/// 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).
/// </summary>
/// <param name="levelId">Level identifier (LayoutLevel.Id)</param>
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;
}
/// <summary>
/// Unsubscribe from monitor updates for the current level
/// </summary>
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);
}
}