using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.SignalR.Client; using RobotNet10.RobotApp.Client.Shared.Motion; namespace RobotNet10.RobotApp.Client.Clients; /// /// SignalR client kết nối tới OdometryHub - hiển thị odom (pose + velocity) realtime /// public class OdometryHubClient : IAsyncDisposable { private readonly HubConnection _hubConnection; private bool _disposed; /// /// Sự kiện nhận odometry mới (broadcast từ server ~10 Hz) /// public event Action? OdometryReceived; /// /// Trạng thái kết nối thay đổi /// public event Action? ConnectionStateChanged; public bool IsConnected => _hubConnection.State == HubConnectionState.Connected; public HubConnectionState ConnectionState => _hubConnection.State; public OdometryHubClient(NavigationManager navigationManager) { var hubUrl = navigationManager.ToAbsoluteUri("/hubs/odometry"); _hubConnection = new HubConnectionBuilder() .WithUrl(hubUrl) .WithAutomaticReconnect() .Build(); _hubConnection.On("ReceiveOdometry", dto => OdometryReceived?.Invoke(dto)); _hubConnection.Reconnecting += _ => { ConnectionStateChanged?.Invoke(_hubConnection.State); return Task.CompletedTask; }; _hubConnection.Reconnected += _ => { ConnectionStateChanged?.Invoke(_hubConnection.State); return Task.CompletedTask; }; _hubConnection.Closed += _ => { ConnectionStateChanged?.Invoke(_hubConnection.State); return Task.CompletedTask; }; } public async Task StartAsync() { if (_hubConnection.State == HubConnectionState.Disconnected) { await _hubConnection.StartAsync(); ConnectionStateChanged?.Invoke(_hubConnection.State); } } public async Task StopAsync() { if (_hubConnection.State != HubConnectionState.Disconnected) { await _hubConnection.StopAsync(); ConnectionStateChanged?.Invoke(_hubConnection.State); } } /// /// Lấy odometry hiện tại (on-demand) /// public async Task GetCurrentOdometryAsync() { return await _hubConnection.InvokeAsync("GetCurrentOdometry"); } public async ValueTask DisposeAsync() { if (_disposed) return; _disposed = true; await StopAsync(); await _hubConnection.DisposeAsync(); GC.SuppressFinalize(this); } }