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);
}
}