using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.SignalR.Client; using RobotNet10.RobotApp.Client.Shared.SLAM; using RobotNet10.RobotApp.Shared.Enums; using RobotNet10.Shared.Geometry; using RobotNet10.Shared.Numbers; namespace RobotNet10.RobotApp.Client.Clients; /// /// SignalR client để kết nối với CartographerHub /// public class SLAMClient : IAsyncDisposable { private readonly HubConnection _hubConnection; private bool _disposed; // Events từ server public event Action? StateChanged; public event Action? MapSaveProgressChanged; public event Action? IsProcessingChanged; public bool IsConnected => _hubConnection.State == HubConnectionState.Connected; public HubConnectionState ConnectionState => _hubConnection.State; // Cached state public SLAMState? CurrentState { get; private set; } public PoseDto? CurrentPose { get; private set; } public SLAMClient(NavigationManager navigationManager) { var hubUrl = navigationManager.ToAbsoluteUri("/hubs/slam"); _hubConnection = new HubConnectionBuilder() .WithUrl(hubUrl) .WithAutomaticReconnect() .Build(); // Subscribe to server events _hubConnection.On("OnStateChanged", state => { CurrentState = state; StateChanged?.Invoke(state); }); _hubConnection.On("OnMapSaveProgress", (workItemsAdded, workItemsCompleted, percentComplete) => { MapSaveProgressChanged?.Invoke(workItemsAdded, workItemsCompleted, percentComplete); }); _hubConnection.On("OnMapProcessingChanged", (mapName, isProcessing) => { IsProcessingChanged?.Invoke(mapName, isProcessing); }); _hubConnection.Reconnected += connectionId => { // Refresh state after reconnect _ = RefreshStateAsync(); return Task.CompletedTask; }; } public async Task StartAsync() { if (_hubConnection.State == HubConnectionState.Disconnected) { await _hubConnection.StartAsync(); // Get initial state await RefreshStateAsync(); } } public async Task StopAsync() { if (_hubConnection.State != HubConnectionState.Disconnected) { await _hubConnection.StopAsync(); } } /// /// Refresh current state from server /// public async Task RefreshStateAsync() { if (IsConnected) { try { CurrentState = await _hubConnection.InvokeAsync("GetCurrentState"); } catch { // Ignore errors } } } // Server method invocations public async Task GetCurrentStateAsync() { var state = await _hubConnection.InvokeAsync("GetCurrentState"); CurrentState = state; return state; } /// /// Lấy tên map hiện tại đang được sử dụng /// public async Task GetCurrentMapAsync() { return await _hubConnection.InvokeAsync("GetCurrentMap"); } public async Task StartLocalizationAsync(string mapName, PoseDto? initialPose = null) { return await _hubConnection.InvokeAsync("StartLocalization", mapName, initialPose); } public async Task StopLocalizationAsync() { await _hubConnection.InvokeAsync("StopLocalization"); } public async Task StartScanMappingAsync(string mapName) { return await _hubConnection.InvokeAsync("StartScanMapping", mapName); } public async Task SaveMapAsync() { return await _hubConnection.InvokeAsync("SaveMap"); } public async Task ListMapsAsync() { return await _hubConnection.InvokeAsync("ListMaps"); } /// /// Lấy thông tin map và đăng ký nhận cập nhật trạng thái xử lý /// /// Tên map /// MapInfoDto với trạng thái IsProcessing public async Task GetMapInfoAndSubscribeProcessingAsync(string mapName) { return await _hubConnection.InvokeAsync("GetMapInfoAndSubscribeProcessing", mapName); } /// /// Hủy đăng ký nhận cập nhật trạng thái xử lý /// /// Tên map public async Task UnsubscribeMapProcessingAsync(string mapName) { await _hubConnection.InvokeAsync("UnsubscribeMapProcessing", mapName); } public async Task DeleteMapAsync(string mapName) { return await _hubConnection.InvokeAsync("DeleteMap", mapName); } /// /// Transform map origin to a new pose /// /// Name of the map to transform /// New origin pose (position and orientation) /// True if transform was successful public async Task TransformMapOriginAsync(string mapName, PoseDto newOrigin) { return await _hubConnection.InvokeAsync("TransformMapOrigin", mapName, newOrigin); } /// /// Rerender map image files (PNG, JPG, PGM) with custom OccupancyGridConfiguration. /// This is an async operation - returns true if processing started successfully. /// Monitor IsProcessingChanged event for completion notification. /// /// Name of the map to rerender /// Custom occupancy grid configuration /// True if rerender was started successfully public async Task RerenderMapWithConfigAsync(string mapName, OccupancyGridConfigurationDto config) { return await _hubConnection.InvokeAsync("RerenderMapWithConfig", mapName, config); } public async Task SetInitialPoseAsync(PoseDto pose) { await _hubConnection.InvokeAsync("SetInitialPose", pose); } /// /// Lấy occupancy grid mới hơn thời gian chỉ định /// /// Thời gian để so sánh /// OccupancyGridDto nếu có update mới hơn, null nếu không có public async Task GetOccupancyGridAsync(DateTime since) { var grid = await _hubConnection.InvokeAsync("GetOccupancyGrid", since); return grid; } /// /// Lấy pose hiện tại của robot /// /// PoseDto nếu có pose, null nếu không có public async Task GetCurrentPoseAsync() { var pose = await _hubConnection.InvokeAsync("GetCurrentPose"); if (pose != null) { CurrentPose = pose; } return pose; } /// /// Lấy sample point cloud từ tất cả lidar devices (trong global frame) /// /// Danh sách Point32 trong global frame public async Task GetSamplePointCloudAsync() { return await _hubConnection.InvokeAsync("GetSamplePointCloud") ?? []; } public async ValueTask DisposeAsync() { if (_disposed) return; _disposed = true; await StopAsync(); await _hubConnection.DisposeAsync(); GC.SuppressFinalize(this); } }