Initial commit

This commit is contained in:
2026-07-03 16:37:12 +07:00
commit 63b8c1ea8b
1931 changed files with 640587 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.SignalR.Client;
using RobotNet10.RobotApp.Client.Shared.Devices;
using RobotNet10.Shared.Sensor;
namespace RobotNet10.RobotApp.Client.Clients;
/// <summary>
/// SignalR client để kết nối với BatteryHub
/// </summary>
public class BatteryHubClient : IAsyncDisposable
{
private readonly HubConnection _hubConnection;
private bool _disposed;
public bool IsConnected => _hubConnection.State == HubConnectionState.Connected;
public HubConnectionState ConnectionState => _hubConnection.State;
public BatteryHubClient(NavigationManager navigationManager)
{
var hubUrl = navigationManager.ToAbsoluteUri("/hubs/devices/battery");
_hubConnection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.WithAutomaticReconnect()
.Build();
_hubConnection.Reconnecting += error =>
{
return Task.CompletedTask;
};
_hubConnection.Reconnected += connectionId =>
{
return Task.CompletedTask;
};
_hubConnection.Closed += error =>
{
return Task.CompletedTask;
};
}
public async Task StartAsync()
{
if (_hubConnection.State == HubConnectionState.Disconnected)
{
await _hubConnection.StartAsync();
}
}
public async Task StopAsync()
{
if (_hubConnection.State != HubConnectionState.Disconnected)
{
await _hubConnection.StopAsync();
}
}
public async Task<BatteryState> GetBatteryDataAsync(string deviceId)
{
var result = await _hubConnection.InvokeAsync<BatteryState?>("GetBatteryData", deviceId);
return result ?? new BatteryState();
}
public async Task<DeviceInfoDto?> GetDeviceInfoAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<DeviceInfoDto?>("GetDeviceInfo", deviceId);
}
public async ValueTask DisposeAsync()
{
if (_disposed)
return;
_disposed = true;
await StopAsync();
await _hubConnection.DisposeAsync();
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,81 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.SignalR.Client;
using RobotNet10.RobotApp.Client.Shared.Devices;
using RobotNet10.Shared.Geometry;
namespace RobotNet10.RobotApp.Client.Clients;
/// <summary>
/// SignalR client để kết nối với CameraQrHub
/// </summary>
public class CameraQrHubClient : IAsyncDisposable
{
private readonly HubConnection _hubConnection;
private bool _disposed;
public bool IsConnected => _hubConnection.State == HubConnectionState.Connected;
public HubConnectionState ConnectionState => _hubConnection.State;
public CameraQrHubClient(NavigationManager navigationManager)
{
var hubUrl = navigationManager.ToAbsoluteUri("/hubs/devices/cameraqr");
_hubConnection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.WithAutomaticReconnect()
.Build();
_hubConnection.Reconnecting += error =>
{
return Task.CompletedTask;
};
_hubConnection.Reconnected += connectionId =>
{
return Task.CompletedTask;
};
_hubConnection.Closed += error =>
{
return Task.CompletedTask;
};
}
public async Task StartAsync()
{
if (_hubConnection.State == HubConnectionState.Disconnected)
{
await _hubConnection.StartAsync();
}
}
public async Task StopAsync()
{
if (_hubConnection.State != HubConnectionState.Disconnected)
{
await _hubConnection.StopAsync();
}
}
public async Task<CameraQrDataDto> GetCameraQrDataAsync(string deviceId)
{
var result = await _hubConnection.InvokeAsync<CameraQrDataDto?>("GetCameraQrData", deviceId);
return result ?? new CameraQrDataDto();
}
public async Task<DeviceInfoDto?> GetDeviceInfoAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<DeviceInfoDto?>("GetDeviceInfo", deviceId);
}
public async ValueTask DisposeAsync()
{
if (_disposed)
return;
_disposed = true;
await StopAsync();
await _hubConnection.DisposeAsync();
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,322 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.SignalR.Client;
using RobotNet10.RobotApp.Client.Shared.Devices;
namespace RobotNet10.RobotApp.Client.Clients;
/// <summary>
/// SignalR client để kết nối với CiA402ServoHub
/// </summary>
public class CiA402ServoHubClient : IAsyncDisposable
{
private readonly HubConnection _hubConnection;
private bool _disposed;
public bool IsConnected => _hubConnection.State == HubConnectionState.Connected;
public HubConnectionState ConnectionState => _hubConnection.State;
public CiA402ServoHubClient(NavigationManager navigationManager)
{
var hubUrl = navigationManager.ToAbsoluteUri("/hubs/devices/cia402servo");
_hubConnection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.WithAutomaticReconnect()
.Build();
_hubConnection.Reconnecting += error =>
{
return Task.CompletedTask;
};
_hubConnection.Reconnected += connectionId =>
{
return Task.CompletedTask;
};
_hubConnection.Closed += error =>
{
return Task.CompletedTask;
};
}
public async Task StartAsync()
{
if (_hubConnection.State == HubConnectionState.Disconnected)
{
await _hubConnection.StartAsync();
}
}
public async Task StopAsync()
{
if (_hubConnection.State != HubConnectionState.Disconnected)
{
await _hubConnection.StopAsync();
}
}
public async Task<CiA402ServoDataDto> GetServoDataAsync(string deviceId)
{
var result = await _hubConnection.InvokeAsync<CiA402ServoDataDto?>("GetServoData", deviceId);
return result ?? new CiA402ServoDataDto();
}
public async Task<DeviceInfoDto?> GetDeviceInfoAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<DeviceInfoDto?>("GetDeviceInfo", deviceId);
}
// State Machine Control Methods
public async Task EnableOperationAsync(string deviceId)
{
await _hubConnection.InvokeAsync("EnableOperationAsync", deviceId);
}
public async Task DisableOperationAsync(string deviceId)
{
await _hubConnection.InvokeAsync("DisableOperationAsync", deviceId);
}
public async Task QuickStopAsync(string deviceId)
{
await _hubConnection.InvokeAsync("QuickStopAsync", deviceId);
}
public async Task FaultResetAsync(string deviceId)
{
await _hubConnection.InvokeAsync("FaultResetAsync", deviceId);
}
public async Task ShutdownAsync(string deviceId)
{
await _hubConnection.InvokeAsync("ShutdownAsync", deviceId);
}
public async Task SwitchOnAsync(string deviceId)
{
await _hubConnection.InvokeAsync("SwitchOnAsync", deviceId);
}
public async Task EnableAsync(string deviceId)
{
await _hubConnection.InvokeAsync("EnableAsync", deviceId);
}
public async Task DisableAsync(string deviceId)
{
await _hubConnection.InvokeAsync("DisableAsync", deviceId);
}
// Operation Mode
public async Task SetOperationModeAsync(string deviceId, string mode)
{
await _hubConnection.InvokeAsync("SetOperationModeAsync", deviceId, mode);
}
// Position Control
public async Task SetTargetPositionAsync(string deviceId, int position)
{
await _hubConnection.InvokeAsync("SetTargetPositionAsync", deviceId, position);
}
public async Task MoveToPositionAsync(string deviceId, int position, uint velocity = 1000, uint acceleration = 5000, uint deceleration = 5000)
{
await _hubConnection.InvokeAsync("MoveToPositionAsync", deviceId, position, velocity, acceleration, deceleration);
}
// Velocity Control
public async Task SetTargetVelocityAsync(string deviceId, int velocity)
{
await _hubConnection.InvokeAsync("SetTargetVelocityAsync", deviceId, velocity);
}
public async Task TargetVelocityAsync(string deviceId, int targetVelocity, uint acceleration = 5000, uint deceleration = 5000)
{
await _hubConnection.InvokeAsync("TargetVelocityAsync", deviceId, targetVelocity, acceleration, deceleration);
}
public async Task ProfileVelocityAsync(string deviceId, int targetVelocity, uint acceleration = 5000, uint deceleration = 5000)
{
await _hubConnection.InvokeAsync("ProfileVelocityAsync", deviceId, targetVelocity, acceleration, deceleration);
}
// Torque Control
public async Task SetTargetTorqueAsync(string deviceId, short torque)
{
await _hubConnection.InvokeAsync("SetTargetTorqueAsync", deviceId, torque);
}
public async Task RunTorqueAsync(string deviceId, short torque)
{
await _hubConnection.InvokeAsync("RunTorqueAsync", deviceId, torque);
}
// Profile Settings
public async Task SetProfileAccelerationAsync(string deviceId, uint acceleration)
{
await _hubConnection.InvokeAsync("SetProfileAccelerationAsync", deviceId, acceleration);
}
public async Task SetProfileDecelerationAsync(string deviceId, uint deceleration)
{
await _hubConnection.InvokeAsync("SetProfileDecelerationAsync", deviceId, deceleration);
}
public async Task SetProfileVelocityAsync(string deviceId, uint velocity)
{
await _hubConnection.InvokeAsync("SetProfileVelocityAsync", deviceId, velocity);
}
public async Task SetProfileSpeedAsync(string deviceId, uint velocity)
{
await _hubConnection.InvokeAsync("SetProfileSpeedAsync", deviceId, velocity);
}
/// <summary>
/// Ghi cùng lúc cả 3 profile (speed, acceleration, deceleration) xuống drive.
/// </summary>
public async Task SetProfileSettingsAsync(string deviceId, uint profileSpeed, uint profileAcceleration, uint profileDeceleration)
{
await _hubConnection.InvokeAsync("SetProfileSettingsAsync", deviceId, profileSpeed, profileAcceleration, profileDeceleration);
}
// Homing
public async Task SetHomingMethodAsync(string deviceId, byte method)
{
await _hubConnection.InvokeAsync("SetHomingMethodAsync", deviceId, method);
}
public async Task SetHomingSpeedAsync(string deviceId, int speed)
{
await _hubConnection.InvokeAsync("SetHomingSpeedAsync", deviceId, speed);
}
public async Task SetHomingOffsetAsync(string deviceId, int offset)
{
await _hubConnection.InvokeAsync("SetHomingOffsetAsync", deviceId, offset);
}
public async Task StartHomingAsync(string deviceId, byte method, int speed)
{
await _hubConnection.InvokeAsync("StartHomingAsync", deviceId, method, speed);
}
/// <summary>Đọc lại homing method từ drive để kiểm tra đã ghi xuống chưa.</summary>
public async Task<byte> GetHomingMethodAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<byte>("GetHomingMethodAsync", deviceId);
}
/// <summary>Đọc lại homing speed từ drive để kiểm tra đã ghi xuống chưa.</summary>
public async Task<int> GetHomingSpeedAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<int>("GetHomingSpeedAsync", deviceId);
}
/// <summary>Đọc lại homing offset từ drive để kiểm tra đã ghi xuống chưa.</summary>
public async Task<int> GetHomingOffsetAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<int>("GetHomingOffsetAsync", deviceId);
}
// Position Control - Additional Methods
public async Task StartPositionMoveAsync(string deviceId)
{
await _hubConnection.InvokeAsync("StartPositionMoveAsync", deviceId);
}
public async Task WaitUntilAtTargetAsync(string deviceId, int tolerance = 100, bool useStatusword = true, int checkIntervalMs = 10)
{
await _hubConnection.InvokeAsync("WaitUntilAtTargetAsync", deviceId, tolerance, useStatusword, checkIntervalMs);
}
// Error and Status Information
public async Task<bool> IsInFaultStateAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<bool>("IsInFaultStateAsync", deviceId);
}
public async Task<byte> GetErrorRegisterAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<byte>("GetErrorRegisterAsync", deviceId);
}
public async Task<ushort[]> GetErrorHistoryAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<ushort[]>("GetErrorHistoryAsync", deviceId);
}
public async Task<ushort> GetLatestErrorCodeAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<ushort>("GetLatestErrorCodeAsync", deviceId);
}
public async Task<bool> TryFaultResetAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<bool>("TryFaultResetAsync", deviceId);
}
public async Task<bool> IsEnabledAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<bool>("IsEnabledAsync", deviceId);
}
public async Task<bool> IsReadyAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<bool>("IsReadyAsync", deviceId);
}
// Statusword & Controlword
public async Task<ushort> GetStatuswordAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<ushort>("GetStatuswordAsync", deviceId);
}
public async Task SetControlwordAsync(string deviceId, ushort controlwordValue)
{
await _hubConnection.InvokeAsync("SetControlwordAsync", deviceId, controlwordValue);
}
public async Task<string> GetStateAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<string>("GetStateAsync", deviceId);
}
public async Task<string> GetOperationModeAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<string>("GetOperationModeAsync", deviceId);
}
// Position, Velocity, Torque
public async Task<int> GetActualPositionAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<int>("GetActualPositionAsync", deviceId);
}
public async Task<int> GetActualVelocityAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<int>("GetActualVelocityAsync", deviceId);
}
public async Task<short> GetActualTorqueAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<short>("GetActualTorqueAsync", deviceId);
}
// Target Position Checking
public async Task<bool> IsAtTargetAsync(string deviceId, int tolerance = 100, bool useStatusword = true)
{
return await _hubConnection.InvokeAsync<bool>("IsAtTarget", deviceId, tolerance, useStatusword);
}
public async ValueTask DisposeAsync()
{
if (_disposed)
return;
_disposed = true;
await StopAsync();
await _hubConnection.DisposeAsync();
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,97 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.SignalR.Client;
using RobotNet10.RobotApp.Client.Shared.Devices;
using RobotNet10.Shared.Sensor;
namespace RobotNet10.RobotApp.Client.Clients;
/// <summary>
/// SignalR client để kết nối với InertialMeasurementUnitHub
/// </summary>
public class InertialMeasurementUnitHubClient : IAsyncDisposable
{
private readonly HubConnection _hubConnection;
private bool _disposed;
public bool IsConnected => _hubConnection.State == HubConnectionState.Connected;
public HubConnectionState ConnectionState => _hubConnection.State;
public InertialMeasurementUnitHubClient(NavigationManager navigationManager)
{
var hubUrl = navigationManager.ToAbsoluteUri("/hubs/devices/inertialmeasurementunit");
_hubConnection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.WithAutomaticReconnect()
.Build();
_hubConnection.Reconnecting += error =>
{
return Task.CompletedTask;
};
_hubConnection.Reconnected += connectionId =>
{
return Task.CompletedTask;
};
_hubConnection.Closed += error =>
{
return Task.CompletedTask;
};
}
public async Task StartAsync()
{
if (_hubConnection.State == HubConnectionState.Disconnected)
{
await _hubConnection.StartAsync();
}
}
public async Task StopAsync()
{
if (_hubConnection.State != HubConnectionState.Disconnected)
{
await _hubConnection.StopAsync();
}
}
public async Task<Imu> GetImuDataAsync(string deviceId)
{
var result = await _hubConnection.InvokeAsync<Imu?>("GetImuData", deviceId);
return result ?? new Imu();
}
public async Task<Imu> ReadAllDataAsync(string deviceId)
{
var result = await _hubConnection.InvokeAsync<Imu?>("ReadAllData", deviceId);
return result ?? new Imu();
}
public async Task<DeviceInfoDto?> GetDeviceInfoAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<DeviceInfoDto?>("GetDeviceInfo", deviceId);
}
public async Task<Dictionary<string, string>?> GetDevicePropertiesAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<Dictionary<string, string>?>("GetDeviceProperties", deviceId);
}
public async Task<List<PropertyDescription>?> GetDevicePropertyDescriptionsAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<List<PropertyDescription>?>("GetDevicePropertyDescriptions", deviceId);
}
public async ValueTask DisposeAsync()
{
if (_disposed)
return;
_disposed = true;
await StopAsync();
await _hubConnection.DisposeAsync();
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,82 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.SignalR.Client;
using RobotNet10.RobotApp.Client.Shared.Devices;
using RobotNet10.Shared.Sensor;
namespace RobotNet10.RobotApp.Client.Clients;
/// <summary>
/// SignalR client để kết nối với LidarHub
/// </summary>
public class LidarHubClient : IAsyncDisposable
{
private readonly HubConnection _hubConnection;
private bool _disposed;
public bool IsConnected => _hubConnection.State == HubConnectionState.Connected;
public HubConnectionState ConnectionState => _hubConnection.State;
public LidarHubClient(NavigationManager navigationManager)
{
var hubUrl = navigationManager.ToAbsoluteUri("/hubs/devices/lidar");
_hubConnection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.WithAutomaticReconnect()
.Build();
_hubConnection.Reconnecting += error =>
{
return Task.CompletedTask;
};
_hubConnection.Reconnected += connectionId =>
{
return Task.CompletedTask;
};
_hubConnection.Closed += error =>
{
return Task.CompletedTask;
};
}
public async Task StartAsync()
{
if (_hubConnection.State == HubConnectionState.Disconnected)
{
await _hubConnection.StartAsync();
}
}
public async Task StopAsync()
{
if (_hubConnection.State != HubConnectionState.Disconnected)
{
await _hubConnection.StopAsync();
}
}
public async Task<LaserScan> GetLidarDataAsync(string deviceId)
{
var result = await _hubConnection.InvokeAsync<LaserScan?>("GetLidarData", deviceId);
return result ?? new LaserScan();
}
public async Task<DeviceInfoDto?> GetDeviceInfoAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<DeviceInfoDto?>("GetDeviceInfo", deviceId);
}
public async ValueTask DisposeAsync()
{
if (_disposed)
return;
_disposed = true;
await StopAsync();
await _hubConnection.DisposeAsync();
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,77 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.SignalR.Client;
using RobotNet10.Shared;
using RobotNet10.Shared.Detection;
using RobotNet10.Shared.Geometry;
namespace RobotNet10.RobotApp.Client.Clients;
public class MarkerDetectorHubClient : IAsyncDisposable
{
private readonly HubConnection _hubConnection;
private bool _disposed;
public bool IsConnected => _hubConnection.State == HubConnectionState.Connected;
public HubConnectionState ConnectionState => _hubConnection.State;
public MarkerDetectorHubClient(NavigationManager navigationManager)
{
var hubUrl = navigationManager.ToAbsoluteUri("hubs/marker-detect");
_hubConnection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.WithAutomaticReconnect()
.Build();
_hubConnection.Reconnecting += error =>
{
return Task.CompletedTask;
};
_hubConnection.Reconnected += connectionId =>
{
return Task.CompletedTask;
};
_hubConnection.Closed += error =>
{
return Task.CompletedTask;
};
}
public async Task StartAsync()
{
if (_hubConnection.State == HubConnectionState.Disconnected)
{
await _hubConnection.StartAsync();
}
}
public async Task StopAsync()
{
if (_hubConnection.State != HubConnectionState.Disconnected)
{
await _hubConnection.StopAsync();
}
}
public async Task<MessageResult<Guid>> CreateSessionAsync(MarkersSearchRequest request)
{
return await _hubConnection.InvokeAsync<MessageResult<Guid>>("CreateSession", request);
}
public async Task<MessageResult<Pose>> GetGoalAsync(Guid sessionId)
{
return await _hubConnection.InvokeAsync<MessageResult<Pose>>("GetGoal", sessionId);
}
public async ValueTask DisposeAsync()
{
if (_disposed)
return;
_disposed = true;
await StopAsync();
await _hubConnection.DisposeAsync();
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,100 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.SignalR.Client;
using RobotNet10.RobotApp.Client.Shared.Devices;
using RobotNet10.Shared;
namespace RobotNet10.RobotApp.Client.Clients;
/// <summary>
/// SignalR client để kết nối với ModbusTcpHub
/// </summary>
public class ModbusTcpHubClient : IAsyncDisposable
{
private readonly HubConnection _hubConnection;
private bool _disposed;
public bool IsConnected => _hubConnection.State == HubConnectionState.Connected;
public HubConnectionState ConnectionState => _hubConnection.State;
public ModbusTcpHubClient(NavigationManager navigationManager)
{
var hubUrl = navigationManager.ToAbsoluteUri("/hubs/devices/modbustcp");
_hubConnection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.WithAutomaticReconnect()
.Build();
_hubConnection.Reconnecting += error =>
{
return Task.CompletedTask;
};
_hubConnection.Reconnected += connectionId =>
{
return Task.CompletedTask;
};
_hubConnection.Closed += error =>
{
return Task.CompletedTask;
};
}
public async Task StartAsync()
{
if (_hubConnection.State == HubConnectionState.Disconnected)
{
await _hubConnection.StartAsync();
}
}
public async Task StopAsync()
{
if (_hubConnection.State != HubConnectionState.Disconnected)
{
await _hubConnection.StopAsync();
}
}
public async Task<ModbusTcpData> GetModbusDataAsync(string deviceId)
{
var result = await _hubConnection.InvokeAsync<ModbusTcpData?>("GetModbusData", deviceId);
return result ?? new ModbusTcpData();
}
public async Task<DeviceInfoDto?> GetDeviceInfoAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<DeviceInfoDto?>("GetDeviceInfo", deviceId);
}
/// <summary>
/// Ghi một coil vào device
/// </summary>
/// <param name="deviceId">Device ID</param>
/// <param name="address">Địa chỉ coil</param>
/// <param name="value">Giá trị cần ghi (true/false)</param>
/// <returns>True nếu thành công, false nếu thất bại</returns>
public async Task<bool> WriteCoilAsync(string deviceId, ushort address, bool value)
{
try
{
return await _hubConnection.InvokeAsync<bool>("WriteCoil", deviceId, address, value);
}
catch
{
return false;
}
}
public async ValueTask DisposeAsync()
{
if (_disposed)
return;
_disposed = true;
await StopAsync();
await _hubConnection.DisposeAsync();
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,262 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.SignalR.Client;
using RobotNet10.RobotApp.Client.Shared.Motion;
using RobotNet10.RobotApp.Client.Shared.Modules;
namespace RobotNet10.RobotApp.Client.Clients;
/// <summary>
/// SignalR client thống nhất để kết nối với MotionHub
/// Bao gồm: ManualControl, Odometry, LiftModule, RotationModule
/// </summary>
public class MotionHubClient : IAsyncDisposable
{
private readonly HubConnection _hubConnection;
private bool _disposed;
public bool IsConnected => _hubConnection.State == HubConnectionState.Connected;
public HubConnectionState ConnectionState => _hubConnection.State;
/// <summary>
/// Event fired when odometry pose changes
/// </summary>
public event Action<OdometryDto>? PoseUpdated;
/// <summary>
/// Event fired when connection state changes
/// </summary>
public event Action<HubConnectionState>? ConnectionStateChanged;
public MotionHubClient(NavigationManager navigationManager)
{
var hubUrl = navigationManager.ToAbsoluteUri("/hubs/motion");
_hubConnection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.WithAutomaticReconnect()
.Build();
// Subscribe to pose updates from server
_hubConnection.On<OdometryDto>("PoseUpdated", dto => PoseUpdated?.Invoke(dto));
_hubConnection.Reconnecting += async (ex) =>
{
ConnectionStateChanged?.Invoke(_hubConnection.State);
await Task.CompletedTask;
};
_hubConnection.Reconnected += async (id) =>
{
ConnectionStateChanged?.Invoke(_hubConnection.State);
await Task.CompletedTask;
};
_hubConnection.Closed += async (ex) =>
{
ConnectionStateChanged?.Invoke(_hubConnection.State);
await Task.CompletedTask;
};
}
/// <summary>
/// Connect to hub
/// </summary>
public async Task StartAsync()
{
if (_hubConnection.State == HubConnectionState.Disconnected)
{
await _hubConnection.StartAsync();
ConnectionStateChanged?.Invoke(_hubConnection.State);
}
}
/// <summary>
/// Disconnect from hub
/// </summary>
public async Task StopAsync()
{
if (_hubConnection.State != HubConnectionState.Disconnected)
{
await _hubConnection.StopAsync();
}
}
#region Manual Control
/// <summary>
/// Get current status
/// </summary>
public async Task<ManualControlStatusDto> GetStatusAsync()
{
return await _hubConnection.InvokeAsync<ManualControlStatusDto>("GetStatus");
}
/// <summary>
/// Enable manual control
/// </summary>
public async Task EnableAsync()
{
await _hubConnection.InvokeAsync("Enable");
}
/// <summary>
/// Disable manual control
/// </summary>
public async Task DisableAsync()
{
await _hubConnection.InvokeAsync("Disable");
}
#endregion
#region Odometry
/// <summary>
/// Get current odometry pose
/// </summary>
public async Task<OdometryDto> GetCurrentPoseAsync()
{
return await _hubConnection.InvokeAsync<OdometryDto>("GetCurrentPose");
}
/// <summary>
/// Reset odometry to initial position
/// </summary>
public async Task ResetOdometryAsync()
{
await _hubConnection.InvokeAsync("ResetOdometry");
}
/// <summary>
/// Reset odometry to a specific pose
/// </summary>
public async Task ResetOdometryPoseAsync(OdometryDto dto)
{
await _hubConnection.InvokeAsync("ResetOdometryPose", dto);
}
#endregion
#region Lift Module
/// <summary>
/// Get lift module status
/// </summary>
public async Task<LiftModuleStatusDto> GetLiftStatusAsync()
{
return await _hubConnection.InvokeAsync<LiftModuleStatusDto>("GetLiftStatus");
}
/// <summary>
/// Get current lift position
/// </summary>
public async Task<int> GetLiftCurrentPositionAsync()
{
return await _hubConnection.InvokeAsync<int>("GetLiftCurrentPosition");
}
/// <summary>
/// Lift up
/// </summary>
public async Task LiftUpAsync()
{
await _hubConnection.InvokeAsync("LiftUp");
}
/// <summary>
/// Lift down
/// </summary>
public async Task LiftDownAsync()
{
await _hubConnection.InvokeAsync("LiftDown");
}
/// <summary>
/// Stop lift movement (when moving up/down)
/// </summary>
public async Task LiftStopAsync()
{
await _hubConnection.InvokeAsync("LiftStop");
}
/// <summary>
/// Move lift to position
/// </summary>
public async Task LiftToPositionAsync(int position)
{
await _hubConnection.InvokeAsync("LiftToPosition", position);
}
/// <summary>
/// Check if lift module is homed
/// </summary>
public async Task<bool> IsLiftHomedAsync()
{
return await _hubConnection.InvokeAsync<bool>("IsLiftHomed");
}
/// <summary>
/// Manual homing for lift module
/// </summary>
public async Task LiftHomeAsync()
{
await _hubConnection.InvokeAsync("LiftHome");
}
#endregion
#region Rotation Module
/// <summary>
/// Get rotation module status
/// </summary>
public async Task<RotationModuleStatusDto> GetRotationStatusAsync()
{
return await _hubConnection.InvokeAsync<RotationModuleStatusDto>("GetRotationStatus");
}
/// <summary>
/// Get current rotation angle
/// </summary>
public async Task<double> GetRotationCurrentAngleAsync()
{
return await _hubConnection.InvokeAsync<double>("GetRotationCurrentAngle");
}
/// <summary>
/// Rotate to absolute angle
/// </summary>
public async Task RotateToAngleAsync(double angleDegrees)
{
await _hubConnection.InvokeAsync("RotateToAngle", angleDegrees);
}
/// <summary>
/// Rotate relative offset
/// </summary>
public async Task RotateOffsetAsync(double angleOffsetDegrees)
{
await _hubConnection.InvokeAsync("RotateOffset", angleOffsetDegrees);
}
/// <summary>
/// Check if rotation module is homed
/// </summary>
public async Task<bool> IsRotationHomedAsync()
{
return await _hubConnection.InvokeAsync<bool>("IsRotationHomed");
}
#endregion
public async ValueTask DisposeAsync()
{
if (_disposed)
return;
_disposed = true;
await StopAsync();
await _hubConnection.DisposeAsync();
}
}

View File

@@ -0,0 +1,104 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.SignalR.Client;
using RobotNet10.RobotApp.Shared.NavigationMonitor;
namespace RobotNet10.RobotApp.Client.Clients;
public class NavigationMonitorHubClient : IAsyncDisposable
{
private readonly HubConnection _hubConnection;
private bool _disposed;
public bool IsConnected => _hubConnection.State == HubConnectionState.Connected;
public HubConnectionState ConnectionState => _hubConnection.State;
public event Action<NavigationTelemetryDto>? TelemetryReceived;
public event Action<NavigationSafetyViolationDto>? SafetyViolationReceived;
public event Action<NavigationMonitorStateDto>? MonitorStateChanged;
public event Action<HubConnectionState>? ConnectionStateChanged;
public NavigationMonitorHubClient(NavigationManager navigationManager)
{
var hubUrl = navigationManager.ToAbsoluteUri("/hubs/motion/navigation-monitor");
_hubConnection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.WithAutomaticReconnect()
.Build();
_hubConnection.On<NavigationTelemetryDto>("ReceiveTelemetry", dto => TelemetryReceived?.Invoke(dto));
_hubConnection.On<NavigationSafetyViolationDto>("ReceiveSafetyViolation", dto => SafetyViolationReceived?.Invoke(dto));
_hubConnection.On<NavigationMonitorStateDto>("ReceiveMonitorState", dto => MonitorStateChanged?.Invoke(dto));
_hubConnection.Reconnecting += async (_) =>
{
ConnectionStateChanged?.Invoke(_hubConnection.State);
await Task.CompletedTask;
};
_hubConnection.Reconnected += async (_) =>
{
ConnectionStateChanged?.Invoke(_hubConnection.State);
// Re-subscribe after reconnect
await _hubConnection.InvokeAsync("Subscribe");
};
_hubConnection.Closed += async (_) =>
{
ConnectionStateChanged?.Invoke(_hubConnection.State);
await Task.CompletedTask;
};
}
public async Task StartAsync()
{
if (_hubConnection.State == HubConnectionState.Disconnected)
{
await _hubConnection.StartAsync();
ConnectionStateChanged?.Invoke(_hubConnection.State);
await _hubConnection.InvokeAsync("Subscribe");
}
}
public async Task StopAsync()
{
if (_hubConnection.State != HubConnectionState.Disconnected)
{
try { await _hubConnection.InvokeAsync("Unsubscribe"); } catch { }
await _hubConnection.StopAsync();
}
}
public async Task SetTelemetryEnabledAsync(bool enabled)
{
await _hubConnection.InvokeAsync("SetTelemetryEnabled", enabled);
}
public async Task SetSafetyStopEnabledAsync(bool enabled)
{
await _hubConnection.InvokeAsync("SetSafetyStopEnabled", enabled);
}
public async Task UpdateSafetyConfigAsync(NavigationSafetyConfigDto config)
{
await _hubConnection.InvokeAsync("UpdateSafetyConfig", config);
}
public async Task ReleaseSafetyStopAsync()
{
await _hubConnection.InvokeAsync("ReleaseSafetyStop");
}
public async Task<NavigationMonitorStateDto> GetStateAsync()
{
return await _hubConnection.InvokeAsync<NavigationMonitorStateDto>("GetState");
}
public async ValueTask DisposeAsync()
{
if (_disposed) return;
_disposed = true;
await StopAsync();
await _hubConnection.DisposeAsync();
}
}

View File

@@ -0,0 +1,90 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.SignalR.Client;
using RobotNet10.RobotApp.Client.Shared.Motion;
namespace RobotNet10.RobotApp.Client.Clients;
/// <summary>
/// SignalR client kết nối tới OdometryHub - hiển thị odom (pose + velocity) realtime
/// </summary>
public class OdometryHubClient : IAsyncDisposable
{
private readonly HubConnection _hubConnection;
private bool _disposed;
/// <summary>
/// Sự kiện nhận odometry mới (broadcast từ server ~10 Hz)
/// </summary>
public event Action<OdometryDto>? OdometryReceived;
/// <summary>
/// Trạng thái kết nối thay đổi
/// </summary>
public event Action<HubConnectionState>? 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<OdometryDto>("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);
}
}
/// <summary>
/// Lấy odometry hiện tại (on-demand)
/// </summary>
public async Task<OdometryDto?> GetCurrentOdometryAsync()
{
return await _hubConnection.InvokeAsync<OdometryDto?>("GetCurrentOdometry");
}
public async ValueTask DisposeAsync()
{
if (_disposed)
return;
_disposed = true;
await StopAsync();
await _hubConnection.DisposeAsync();
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,92 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.SignalR.Client;
using RobotNet10.RobotApp.Client.Shared.Plc;
namespace RobotNet10.RobotApp.Client.Clients;
/// <summary>
/// SignalR client để kết nối với PlcControllerHub (read-only)
/// </summary>
public class PlcControllerHubClient : IAsyncDisposable
{
private readonly HubConnection _hubConnection;
private bool _disposed;
public bool IsConnected => _hubConnection.State == HubConnectionState.Connected;
public HubConnectionState ConnectionState => _hubConnection.State;
/// <summary>
/// Event fired when connection state changes
/// </summary>
public event Action<HubConnectionState>? ConnectionStateChanged;
public PlcControllerHubClient(NavigationManager navigationManager)
{
var hubUrl = navigationManager.ToAbsoluteUri("/hubs/plc/controller");
_hubConnection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.WithAutomaticReconnect()
.Build();
_hubConnection.Reconnecting += async (ex) =>
{
ConnectionStateChanged?.Invoke(_hubConnection.State);
await Task.CompletedTask;
};
_hubConnection.Reconnected += async (id) =>
{
ConnectionStateChanged?.Invoke(_hubConnection.State);
await Task.CompletedTask;
};
_hubConnection.Closed += async (ex) =>
{
ConnectionStateChanged?.Invoke(_hubConnection.State);
await Task.CompletedTask;
};
}
/// <summary>
/// Connect to hub
/// </summary>
public async Task StartAsync()
{
if (_hubConnection.State == HubConnectionState.Disconnected)
{
await _hubConnection.StartAsync();
ConnectionStateChanged?.Invoke(_hubConnection.State);
}
}
/// <summary>
/// Disconnect from hub
/// </summary>
public async Task StopAsync()
{
if (_hubConnection.State != HubConnectionState.Disconnected)
{
await _hubConnection.StopAsync();
}
}
/// <summary>
/// Get current status
/// </summary>
public async Task<PlcControllerStatusDto> GetStatusAsync()
{
return await _hubConnection.InvokeAsync<PlcControllerStatusDto>("GetStatus");
}
public async ValueTask DisposeAsync()
{
if (_disposed)
return;
_disposed = true;
await StopAsync();
await _hubConnection.DisposeAsync();
}
}

View File

@@ -0,0 +1,86 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.SignalR.Client;
using RobotNet10.RobotApp.Client.Shared.Devices;
namespace RobotNet10.RobotApp.Client.Clients;
public class RfHandleHubClient : IAsyncDisposable
{
private readonly HubConnection _hubConnection;
private bool _disposed;
public bool IsConnected => _hubConnection.State == HubConnectionState.Connected;
public HubConnectionState ConnectionState => _hubConnection.State;
public RfHandleHubClient(NavigationManager navigationManager)
{
var hubUrl = navigationManager.ToAbsoluteUri("/hubs/devices/rfhandle");
_hubConnection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.WithAutomaticReconnect()
.Build();
_hubConnection.Reconnecting += async (ex) =>
{
// optionally notify UI
await Task.CompletedTask;
};
_hubConnection.Reconnected += async (id) =>
{
// optionally notify UI
await Task.CompletedTask;
};
_hubConnection.Closed += async (ex) =>
{
// optionally notify UI
await Task.CompletedTask;
};
}
// CONNECT / DISCONNECT
public async Task StartAsync()
{
if (_hubConnection.State == HubConnectionState.Disconnected)
{
await _hubConnection.StartAsync();
}
}
public async Task StopAsync()
{
if (_hubConnection.State != HubConnectionState.Disconnected)
await _hubConnection.StopAsync();
}
// SERVER CALLS (RPC)
public async Task<RfHandleDataDto> GetRfHandleDataAsync(string deviceId)
{
var result = await _hubConnection.InvokeAsync<RfHandleDataDto?>("GetRfHandleData", deviceId);
return result ?? new RfHandleDataDto();
}
public async Task<DeviceInfoDto?> GetDeviceInfoAsync(string deviceId)
{
return await _hubConnection.InvokeAsync<DeviceInfoDto?>("GetDeviceInfo", deviceId);
}
// DISPOSE
public async ValueTask DisposeAsync()
{
if (_disposed)
return;
_disposed = true;
try
{
// stop but don't throw
await StopAsync();
await _hubConnection.DisposeAsync();
}
catch { }
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,238 @@
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);
}
}