228 lines
7.3 KiB
C#
228 lines
7.3 KiB
C#
using Microsoft.AspNetCore.Components;
|
|
using Microsoft.AspNetCore.SignalR.Client;
|
|
using RobotNet.VDA5050.State;
|
|
using RobotNet10.FleetManager.Shared.DTOs.Robot;
|
|
|
|
namespace RobotNet10.FleetManager.Client.Services;
|
|
|
|
/// <summary>
|
|
/// SignalR Hub Client for robot state and visualization updates.
|
|
/// Manages connection to the SignalR hub and provides methods to subscribe/unsubscribe to robot updates.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This client automatically handles reconnection and provides events for state and visualization updates.
|
|
/// Dispose the client when done to properly clean up the connection.
|
|
/// </remarks>
|
|
public class RobotStateHubClient : IAsyncDisposable
|
|
{
|
|
private readonly HubConnection _hubConnection;
|
|
private readonly ILogger<RobotStateHubClient>? _logger;
|
|
|
|
public event Action<RobotMonitorBoardcastData>? OnMonitorBoardcastUpdate;
|
|
public event Action<StateMsg>? OnStateUpdate;
|
|
public event Action<string>? OnConnectionError;
|
|
public event Action? OnMonitorDeactivated;
|
|
|
|
public RobotStateHubClient(NavigationManager navigationManager, ILogger<RobotStateHubClient>? logger = null)
|
|
{
|
|
_logger = logger;
|
|
var hubUrl = navigationManager.ToAbsoluteUri("/hubs/robot-state");
|
|
|
|
_hubConnection = new HubConnectionBuilder()
|
|
.WithUrl(hubUrl)
|
|
.WithAutomaticReconnect()
|
|
.Build();
|
|
|
|
// Register event handlers
|
|
_hubConnection.On<StateMsg>("OnStateUpdate", (state) =>
|
|
{
|
|
OnStateUpdate?.Invoke(state);
|
|
});
|
|
|
|
_hubConnection.On<RobotMonitorBoardcastData>("OnMonitorUpdate", (state) =>
|
|
{
|
|
OnMonitorBoardcastUpdate?.Invoke(state);
|
|
});
|
|
|
|
_hubConnection.On("OnMonitorDeactivated", () =>
|
|
{
|
|
OnMonitorDeactivated?.Invoke();
|
|
});
|
|
|
|
_hubConnection.Closed += async (error) =>
|
|
{
|
|
if (error != null)
|
|
{
|
|
_logger?.LogError(error, "SignalR connection closed with error");
|
|
OnConnectionError?.Invoke(error.Message);
|
|
}
|
|
else
|
|
{
|
|
_logger?.LogInformation("SignalR connection closed");
|
|
}
|
|
await Task.CompletedTask;
|
|
};
|
|
|
|
_hubConnection.Reconnecting += async (error) =>
|
|
{
|
|
_logger?.LogWarning(error, "SignalR connection reconnecting");
|
|
await Task.CompletedTask;
|
|
};
|
|
|
|
_hubConnection.Reconnected += async (connectionId) =>
|
|
{
|
|
_logger?.LogInformation("SignalR connection reconnected with ID {ConnectionId}", connectionId);
|
|
await Task.CompletedTask;
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Connect to the SignalR hub
|
|
/// </summary>
|
|
public async Task ConnectAsync()
|
|
{
|
|
try
|
|
{
|
|
if (_hubConnection.State == HubConnectionState.Disconnected)
|
|
{
|
|
await _hubConnection.StartAsync();
|
|
_logger?.LogInformation("SignalR hub connected");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogError(ex, "Error connecting to SignalR hub");
|
|
OnConnectionError?.Invoke(ex.Message);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disconnect from the SignalR hub
|
|
/// </summary>
|
|
public async Task DisconnectAsync()
|
|
{
|
|
try
|
|
{
|
|
if (_hubConnection.State != HubConnectionState.Disconnected)
|
|
{
|
|
await _hubConnection.StopAsync();
|
|
_logger?.LogInformation("SignalR hub disconnected");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogError(ex, "Error disconnecting from SignalR hub");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Subscribe to receive updates for a specific robot
|
|
/// </summary>
|
|
/// <param name="robotId">Robot identifier (serialNumber)</param>
|
|
public async Task SubscribeToRobotAsync(string robotId)
|
|
{
|
|
try
|
|
{
|
|
if (_hubConnection.State == HubConnectionState.Connected)
|
|
{
|
|
await _hubConnection.InvokeAsync("SubscribeToRobot", robotId);
|
|
_logger?.LogInformation("Subscribed to robot {RobotId}", robotId);
|
|
}
|
|
else
|
|
{
|
|
_logger?.LogWarning("Cannot subscribe to robot {RobotId}: Hub not connected", robotId);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogError(ex, "Error subscribing to robot {RobotId}", robotId);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unsubscribe from updates for a specific robot
|
|
/// </summary>
|
|
/// <param name="robotId">Robot identifier (serialNumber)</param>
|
|
public async Task UnsubscribeFromRobotAsync(string robotId)
|
|
{
|
|
try
|
|
{
|
|
if (_hubConnection.State == HubConnectionState.Connected)
|
|
{
|
|
await _hubConnection.InvokeAsync("UnsubscribeFromRobot", robotId);
|
|
_logger?.LogInformation("Unsubscribed from robot {RobotId}", robotId);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogError(ex, "Error unsubscribing from robot {RobotId}", robotId);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <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 SubscribeToLevelForMonitorAsync(Guid levelId)
|
|
{
|
|
try
|
|
{
|
|
if (_hubConnection.State == HubConnectionState.Connected)
|
|
{
|
|
await _hubConnection.InvokeAsync("SubscribeToLevelForMonitor", levelId);
|
|
_logger?.LogInformation("Subscribed to level {LevelId} for monitor", levelId);
|
|
}
|
|
else
|
|
{
|
|
_logger?.LogWarning("Cannot subscribe to level {LevelId}: Hub not connected", levelId);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogError(ex, "Error subscribing to level {LevelId} for monitor", levelId);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unsubscribe from monitor updates for the current level
|
|
/// </summary>
|
|
public async Task UnsubscribeFromLevelForMonitorAsync()
|
|
{
|
|
try
|
|
{
|
|
if (_hubConnection.State == HubConnectionState.Connected)
|
|
{
|
|
await _hubConnection.InvokeAsync("UnsubscribeFromLevelForMonitor");
|
|
_logger?.LogInformation("Unsubscribed from level for monitor");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogError(ex, "Error unsubscribing from level for monitor");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the current connection state
|
|
/// </summary>
|
|
public HubConnectionState ConnectionState => _hubConnection.State;
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
if (_hubConnection != null)
|
|
{
|
|
await DisconnectAsync();
|
|
await _hubConnection.DisposeAsync();
|
|
}
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
}
|