Files
Denso/srcs/RobotNet10/RobotApp/RobotNet10.RobotApp.Client/Clients/SLAMClient.cs
2026-07-03 16:31:37 +07:00

239 lines
8.0 KiB
C#

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;
/// <summary>
/// SignalR client để kết nối với CartographerHub
/// </summary>
public class SLAMClient : IAsyncDisposable
{
private readonly HubConnection _hubConnection;
private bool _disposed;
// Events từ server
public event Action<SLAMState>? StateChanged;
public event Action<int, int, int>? MapSaveProgressChanged;
public event Action<string, bool>? 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<SLAMState>("OnStateChanged", state =>
{
CurrentState = state;
StateChanged?.Invoke(state);
});
_hubConnection.On<int, int, int>("OnMapSaveProgress", (workItemsAdded, workItemsCompleted, percentComplete) =>
{
MapSaveProgressChanged?.Invoke(workItemsAdded, workItemsCompleted, percentComplete);
});
_hubConnection.On<string, bool>("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();
}
}
/// <summary>
/// Refresh current state from server
/// </summary>
public async Task RefreshStateAsync()
{
if (IsConnected)
{
try
{
CurrentState = await _hubConnection.InvokeAsync<SLAMState>("GetCurrentState");
}
catch
{
// Ignore errors
}
}
}
// Server method invocations
public async Task<SLAMState> GetCurrentStateAsync()
{
var state = await _hubConnection.InvokeAsync<SLAMState>("GetCurrentState");
CurrentState = state;
return state;
}
/// <summary>
/// Lấy tên map hiện tại đang được sử dụng
/// </summary>
public async Task<string?> GetCurrentMapAsync()
{
return await _hubConnection.InvokeAsync<string?>("GetCurrentMap");
}
public async Task<bool> StartLocalizationAsync(string mapName, PoseDto? initialPose = null)
{
return await _hubConnection.InvokeAsync<bool>("StartLocalization", mapName, initialPose);
}
public async Task StopLocalizationAsync()
{
await _hubConnection.InvokeAsync("StopLocalization");
}
public async Task<bool> StartScanMappingAsync(string mapName)
{
return await _hubConnection.InvokeAsync<bool>("StartScanMapping", mapName);
}
public async Task<string?> SaveMapAsync()
{
return await _hubConnection.InvokeAsync<string?>("SaveMap");
}
public async Task<MapInfoDto[]> ListMapsAsync()
{
return await _hubConnection.InvokeAsync<MapInfoDto[]>("ListMaps");
}
/// <summary>
/// Lấy thông tin map và đăng ký nhận cập nhật trạng thái xử lý
/// </summary>
/// <param name="mapName">Tên map</param>
/// <returns>MapInfoDto với trạng thái IsProcessing</returns>
public async Task<MapInfoDto?> GetMapInfoAndSubscribeProcessingAsync(string mapName)
{
return await _hubConnection.InvokeAsync<MapInfoDto?>("GetMapInfoAndSubscribeProcessing", mapName);
}
/// <summary>
/// Hủy đăng ký nhận cập nhật trạng thái xử lý
/// </summary>
/// <param name="mapName">Tên map</param>
public async Task UnsubscribeMapProcessingAsync(string mapName)
{
await _hubConnection.InvokeAsync("UnsubscribeMapProcessing", mapName);
}
public async Task<bool> DeleteMapAsync(string mapName)
{
return await _hubConnection.InvokeAsync<bool>("DeleteMap", mapName);
}
/// <summary>
/// Transform map origin to a new pose
/// </summary>
/// <param name="mapName">Name of the map to transform</param>
/// <param name="newOrigin">New origin pose (position and orientation)</param>
/// <returns>True if transform was successful</returns>
public async Task<bool> TransformMapOriginAsync(string mapName, PoseDto newOrigin)
{
return await _hubConnection.InvokeAsync<bool>("TransformMapOrigin", mapName, newOrigin);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="mapName">Name of the map to rerender</param>
/// <param name="config">Custom occupancy grid configuration</param>
/// <returns>True if rerender was started successfully</returns>
public async Task<bool> RerenderMapWithConfigAsync(string mapName, OccupancyGridConfigurationDto config)
{
return await _hubConnection.InvokeAsync<bool>("RerenderMapWithConfig", mapName, config);
}
public async Task SetInitialPoseAsync(PoseDto pose)
{
await _hubConnection.InvokeAsync("SetInitialPose", pose);
}
/// <summary>
/// Lấy occupancy grid mới hơn thời gian chỉ định
/// </summary>
/// <param name="since">Thời gian để so sánh</param>
/// <returns>OccupancyGridDto nếu có update mới hơn, null nếu không có</returns>
public async Task<OccupancyGridDto?> GetOccupancyGridAsync(DateTime since)
{
var grid = await _hubConnection.InvokeAsync<OccupancyGridDto?>("GetOccupancyGrid", since);
return grid;
}
/// <summary>
/// Lấy pose hiện tại của robot
/// </summary>
/// <returns>PoseDto nếu có pose, null nếu không có</returns>
public async Task<PoseDto?> GetCurrentPoseAsync()
{
var pose = await _hubConnection.InvokeAsync<PoseDto?>("GetCurrentPose");
if (pose != null)
{
CurrentPose = pose;
}
return pose;
}
/// <summary>
/// Lấy sample point cloud từ tất cả lidar devices (trong global frame)
/// </summary>
/// <returns>Danh sách Point32 trong global frame</returns>
public async Task<RobotNet10.Shared.Numbers.Vector3[]> GetSamplePointCloudAsync()
{
return await _hubConnection.InvokeAsync<RobotNet10.Shared.Numbers.Vector3[]>("GetSamplePointCloud") ?? [];
}
public async ValueTask DisposeAsync()
{
if (_disposed)
return;
_disposed = true;
await StopAsync();
await _hubConnection.DisposeAsync();
GC.SuppressFinalize(this);
}
}