RobotApp/RobotApp.Client/Services/RobotStateClient.cs
Đăng Nguyễn 2785a8f161 update
2025-12-30 15:17:42 +07:00

198 lines
5.3 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;
// ================= 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<StateMsg>("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(StateMsg state)
{
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()
{
if (!LatestStates.IsEmpty) return LatestStates.First().Value;
return null;
}
// ================= DISPOSE =================
public async ValueTask DisposeAsync()
{
_started = false;
_isRobotConnected = false;
SetState(RobotClientState.Disconnected);
if (_connection != null)
{
await _connection.DisposeAsync();
_connection = null;
}
}
}