212 lines
5.5 KiB
C#
212 lines
5.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;
|
||
|
||
// ================= SIGNALR CONNECTION STATE =================
|
||
public enum RobotClientState
|
||
{
|
||
Disconnected,
|
||
Connecting,
|
||
Connected,
|
||
Reconnecting
|
||
}
|
||
|
||
// ================= ROBOT STATE CLIENT =================
|
||
public sealed class RobotStateClient : IAsyncDisposable
|
||
{
|
||
private readonly NavigationManager _nav;
|
||
private HubConnection? _connection;
|
||
|
||
private readonly object _lock = new();
|
||
private bool _started;
|
||
|
||
// ================= STATE CACHE =================
|
||
public ConcurrentDictionary<string, StateMsg> LatestStates { get; } = new();
|
||
|
||
// ================= ROBOT CONNECTION =================
|
||
private bool _isRobotConnected;
|
||
public bool IsRobotConnected => _isRobotConnected;
|
||
|
||
// ================= EVENTS =================
|
||
public event Action<string, StateMsg>? OnStateReceived;
|
||
public event Action<StateMsg>? OnStateReceivedAny;
|
||
public event Action<bool>? OnRobotConnectionChanged;
|
||
public event Action<RobotClientState>? OnConnectionStateChanged;
|
||
|
||
public RobotClientState ConnectionState { get; private set; } = RobotClientState.Disconnected;
|
||
|
||
// ================= CTOR =================
|
||
public RobotStateClient(NavigationManager nav)
|
||
{
|
||
_nav = nav;
|
||
}
|
||
|
||
// ================= STATE HELPER =================
|
||
private void SetState(RobotClientState state)
|
||
{
|
||
if (ConnectionState == state)
|
||
return;
|
||
|
||
ConnectionState = state;
|
||
OnConnectionStateChanged?.Invoke(state);
|
||
}
|
||
|
||
// ================= START =================
|
||
public async Task StartAsync(string hubPath = "/hubs/robot")
|
||
{
|
||
lock (_lock)
|
||
{
|
||
if (_started) return;
|
||
_started = true;
|
||
}
|
||
|
||
SetState(RobotClientState.Connecting);
|
||
|
||
_connection = new HubConnectionBuilder()
|
||
.WithUrl(_nav.ToAbsoluteUri(hubPath))
|
||
.WithAutomaticReconnect(new[]
|
||
{
|
||
TimeSpan.Zero,
|
||
TimeSpan.FromSeconds(2),
|
||
TimeSpan.FromSeconds(10)
|
||
})
|
||
.Build();
|
||
|
||
_connection.Reconnecting += _ =>
|
||
{
|
||
SetState(RobotClientState.Reconnecting);
|
||
return Task.CompletedTask;
|
||
};
|
||
|
||
_connection.Reconnected += _ =>
|
||
{
|
||
SetState(RobotClientState.Connected);
|
||
return Task.CompletedTask;
|
||
};
|
||
|
||
_connection.Closed += async _ =>
|
||
{
|
||
_started = false;
|
||
SetState(RobotClientState.Disconnected);
|
||
|
||
if (_connection != null)
|
||
{
|
||
try
|
||
{
|
||
await StartAsync(hubPath);
|
||
}
|
||
catch { }
|
||
}
|
||
};
|
||
|
||
// ================= SIGNALR HANDLERS =================
|
||
|
||
// VDA5050 State
|
||
_connection.On<string>("ReceiveState", HandleState);
|
||
|
||
// Robot connection (bool only)
|
||
_connection.On<bool>("ReceiveRobotConnection", HandleRobotConnection);
|
||
|
||
try
|
||
{
|
||
await _connection.StartAsync();
|
||
SetState(RobotClientState.Connected);
|
||
}
|
||
catch
|
||
{
|
||
_started = false;
|
||
SetState(RobotClientState.Disconnected);
|
||
}
|
||
}
|
||
|
||
// ================= HANDLE STATE =================
|
||
private void HandleState(string stateJson)
|
||
{
|
||
StateMsg? state;
|
||
|
||
try
|
||
{
|
||
state = JsonSerializer.Deserialize<StateMsg>(
|
||
stateJson,
|
||
JsonOptionExtends.Read
|
||
);
|
||
}
|
||
catch
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (state?.SerialNumber == null)
|
||
return;
|
||
|
||
LatestStates[state.SerialNumber] = state;
|
||
|
||
OnStateReceived?.Invoke(state.SerialNumber, state);
|
||
OnStateReceivedAny?.Invoke(state);
|
||
}
|
||
|
||
// ================= HANDLE ROBOT CONNECTION =================
|
||
private void HandleRobotConnection(bool isConnected)
|
||
{
|
||
_isRobotConnected = isConnected;
|
||
OnRobotConnectionChanged?.Invoke(isConnected);
|
||
}
|
||
|
||
// ================= SUBSCRIBE =================
|
||
public async Task SubscribeRobotAsync(string serialNumber)
|
||
{
|
||
if (_connection?.State != HubConnectionState.Connected)
|
||
return;
|
||
|
||
try
|
||
{
|
||
await _connection.InvokeAsync("JoinRobot", serialNumber);
|
||
}
|
||
catch
|
||
{
|
||
// ignore – reconnect sẽ tự join lại
|
||
}
|
||
}
|
||
|
||
public async Task UnsubscribeRobotAsync(string serialNumber)
|
||
{
|
||
if (_connection?.State == HubConnectionState.Connected)
|
||
{
|
||
try
|
||
{
|
||
await _connection.InvokeAsync("LeaveRobot", serialNumber);
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
LatestStates.TryRemove(serialNumber, out _);
|
||
_isRobotConnected = false;
|
||
}
|
||
|
||
// ================= GET CACHE =================
|
||
public StateMsg? GetLatestState(string serialNumber)
|
||
{
|
||
LatestStates.TryGetValue(serialNumber, out var state);
|
||
return state;
|
||
}
|
||
|
||
// ================= DISPOSE =================
|
||
public async ValueTask DisposeAsync()
|
||
{
|
||
_started = false;
|
||
_isRobotConnected = false;
|
||
SetState(RobotClientState.Disconnected);
|
||
|
||
if (_connection != null)
|
||
{
|
||
await _connection.DisposeAsync();
|
||
_connection = null;
|
||
}
|
||
}
|
||
}
|