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,227 @@
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);
}
}