RobotApp/RobotApp.Client/Services/RobotStateClient.cs
2025-12-21 16:32:06 +07:00

189 lines
4.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
// ================= CONNECTION STATE =================
public enum RobotClientState
{
Disconnected,
Connecting,
Connected,
Reconnecting
}
// ================= CLIENT =================
public sealed class RobotStateClient : IAsyncDisposable
{
private readonly NavigationManager _nav;
private HubConnection? _connection;
private readonly object _lock = new();
private bool _started;
public ConcurrentDictionary<string, StateMsg> LatestStates { get; } = new();
// ================= EVENTS =================
public event Action<string, StateMsg>? OnStateReceived;
public event Action<StateMsg>? OnStateReceivedAny;
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 error =>
{
_started = false;
if (_connection != null)
{
try
{
await StartAsync(hubPath);
}
catch { }
}
};
_connection.On<string>("ReceiveState", HandleState);
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);
}
// ================= 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 _);
}
// ================= GET CACHE =================
public StateMsg? GetLatestState(string serialNumber)
{
LatestStates.TryGetValue(serialNumber, out var state);
return state;
}
// ================= DISPOSE =================
public async ValueTask DisposeAsync()
{
_started = false;
SetState(RobotClientState.Disconnected);
if (_connection != null)
{
await _connection.DisposeAsync();
_connection = null;
}
}
}