RobotApp/RobotApp.Client/Services/RobotStateClient.cs
2025-12-21 11:33:11 +07:00

160 lines
4.5 KiB
C#

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.SignalR.Client;
using RobotApp.Common.Shares;
using RobotApp.VDA5050.State;
using System.Collections.Concurrent;
using System.Text.Json;
namespace RobotApp.Client.Services;
public class RobotStateClient : IAsyncDisposable
{
private readonly NavigationManager _nav;
private HubConnection? _connection;
private bool _started;
private readonly object _lock = new();
public ConcurrentDictionary<string, StateMsg> LatestStates { get; } = new();
public event Action<string, StateMsg>? OnStateReceived;
public event Action<StateMsg>? OnStateReceivedAny;
public RobotStateClient(NavigationManager nav)
{
_nav = nav;
}
public async Task StartAsync(string hubPath = "/hubs/robot")
{
lock (_lock)
{
if (_started)
return;
_started = true;
}
if (string.IsNullOrWhiteSpace(hubPath))
throw new ArgumentException("Hub path is empty", nameof(hubPath));
var hubUri = _nav.ToAbsoluteUri(hubPath);
Console.WriteLine($"[SIGNALR] Connecting to {hubUri}");
_connection = new HubConnectionBuilder()
.WithUrl(hubUri)
.WithAutomaticReconnect(new[]
{
TimeSpan.Zero,
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(10)
})
.Build();
_connection.Closed += async error =>
{
Console.WriteLine($"[SIGNALR] Connection closed: {error?.Message}");
_started = false;
await Task.Delay(3000);
if (_connection != null)
{
try
{
await StartAsync(hubPath);
}
catch { }
}
};
_connection.Reconnecting += error =>
{
Console.WriteLine($"[SIGNALR] Reconnecting... {error?.Message}");
return Task.CompletedTask;
};
_connection.Reconnected += connectionId =>
{
Console.WriteLine($"[SIGNALR] Reconnected: {connectionId}");
return Task.CompletedTask;
};
_connection.On<string>("ReceiveState", stateJson =>
{
try
{
var state = JsonSerializer.Deserialize<StateMsg>(
stateJson,
JsonOptionExtends.Read
);
if (state?.SerialNumber == null)
return;
LatestStates[state.SerialNumber] = state;
OnStateReceived?.Invoke(state.SerialNumber, state);
OnStateReceivedAny?.Invoke(state);
Console.WriteLine(
$"[CLIENT] {state.SerialNumber} | " +
$"X={state.AgvPosition?.X:F2}, " +
$"Y={state.AgvPosition?.Y:F2}, " +
$"Battery={state.BatteryState?.BatteryCharge:F1}%"
);
}
catch (Exception ex)
{
Console.WriteLine($"[CLIENT] Deserialize error: {ex.Message}");
}
});
try
{
await _connection.StartAsync();
Console.WriteLine("[SIGNALR] Connected successfully!");
}
catch (Exception ex)
{
_started = false;
Console.WriteLine($"❌ [SIGNALR] Connection failed: {ex.Message}");
}
}
public async Task SubscribeRobotAsync(string serialNumber)
{
if (_connection?.State != HubConnectionState.Connected)
throw new InvalidOperationException("SignalR is not connected");
await _connection.InvokeAsync("JoinRobot", serialNumber);
Console.WriteLine($"[SIGNALR] Subscribed to {serialNumber}");
}
public async Task UnsubscribeRobotAsync(string serialNumber)
{
if (_connection?.State == HubConnectionState.Connected)
{
await _connection.InvokeAsync("LeaveRobot", serialNumber);
}
LatestStates.TryRemove(serialNumber, out _);
Console.WriteLine($"[SIGNALR] Unsubscribed from {serialNumber}");
}
public StateMsg? GetLatestState(string serialNumber)
{
LatestStates.TryGetValue(serialNumber, out var state);
return state;
}
public async ValueTask DisposeAsync()
{
_started = false;
if (_connection != null)
{
await _connection.DisposeAsync();
_connection = null;
Console.WriteLine("[SIGNALR] Disposed");
}
}
}