239 lines
8.0 KiB
C#
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);
|
|
}
|
|
}
|