@using MudBlazor @using Microsoft.AspNetCore.SignalR.Client @using RobotNet10.RobotApp.Client.Clients @using RobotNet10.RobotApp.Client.Shared.Devices @using RobotNet10.Shared.Sensor @using RobotNet10.Shared.Geometry @using RobotNet10.Shared.Numbers @using QuaternionGeometry = RobotNet10.Shared.Geometry.Quaternion @using QuaternionNumbers = RobotNet10.Shared.Numbers.Quaternion @implements IAsyncDisposable @inject InertialMeasurementUnitHubClient ImuHubClient @inject ISnackbar Snackbar @DeviceName @DeviceId
@{ var accelX = Math.Max(-50, Math.Min(50, ImuData.LinearAcceleration.X * 5)); var accelXEnd = 80 + accelX; } X @{ var accelY = Math.Max(-50, Math.Min(50, ImuData.LinearAcceleration.Y * 5)); var accelYEnd = 80 + accelY; } Y @{ var accelZ = Math.Max(-50, Math.Min(50, ImuData.LinearAcceleration.Z * 5)); var zOffset = accelZ * 0.5; } Z @{ var angularVel = Math.Sqrt(ImuData.AngularVelocity.X * ImuData.AngularVelocity.X + ImuData.AngularVelocity.Y * ImuData.AngularVelocity.Y + ImuData.AngularVelocity.Z * ImuData.AngularVelocity.Z); var angularVelNormalized = Math.Max(0, Math.Min(1, angularVel / 5.0)); var rotationSpeed = angularVelNormalized * 360; } Orientation R:@(QuaternionToEuler(ImuData.Orientation).roll.ToString("F2")) P:@(QuaternionToEuler(ImuData.Orientation).pitch.ToString("F2")) Y:@(QuaternionToEuler(ImuData.Orientation).yaw.ToString("F2"))
Acceleration m/s² X: @ImuData.LinearAcceleration.X.ToString("F3") Y: @ImuData.LinearAcceleration.Y.ToString("F3") Z: @ImuData.LinearAcceleration.Z.ToString("F3") Angular Velocity rad/s Roll (X): @ImuData.AngularVelocity.X.ToString("F3") Pitch (Y): @ImuData.AngularVelocity.Y.ToString("F3") Yaw (Z): @ImuData.AngularVelocity.Z.ToString("F3") Orientation rad (Euler) Roll: @QuaternionToEuler(ImuData.Orientation).roll.ToString("F3") Pitch: @QuaternionToEuler(ImuData.Orientation).pitch.ToString("F3") Yaw: @QuaternionToEuler(ImuData.Orientation).yaw.ToString("F3") Quaternion Unit quaternion W: @ImuData.Orientation.W.ToString("F3") X: @ImuData.Orientation.X.ToString("F3") Y: @ImuData.Orientation.Y.ToString("F3") Z: @ImuData.Orientation.Z.ToString("F3") Last Update: @ImuData.Header.Stamp.ToString("yyyy-MM-dd HH:mm:ss") @if (DeviceProperties.Any() && PropertyDescriptions.Any()) { Property Value @foreach (var prop in GetDisplayedProperties()) { @prop.DisplayName @if (!string.IsNullOrWhiteSpace(prop.Description)) { @prop.Description } @if (DeviceProperties.TryGetValue(prop.Key, out var value)) { @FormatPropertyValue(value, prop) } else { - } } }
@code { [Parameter, EditorRequired] public string DeviceId { get; set; } = string.Empty; private string DeviceName { get; set; } = string.Empty; private Imu ImuData = new(); private Dictionary DeviceProperties = new(); private List PropertyDescriptions = new(); private bool IsLoading => !ImuHubClient.IsConnected; private bool IsReloading = false; protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender && !string.IsNullOrEmpty(DeviceId)) { await ConnectAsync(); } await base.OnAfterRenderAsync(firstRender); } private async Task ConnectAsync() { try { await ImuHubClient.StartAsync(); // Lấy DeviceName từ ImuHub var deviceInfo = await ImuHubClient.GetDeviceInfoAsync(DeviceId); if (deviceInfo != null) { DeviceName = deviceInfo.DeviceName; } else { DeviceName = DeviceId; // Fallback to DeviceId if not found } ImuData = await ImuHubClient.GetImuDataAsync(DeviceId); // Lấy device properties và property descriptions var properties = await ImuHubClient.GetDevicePropertiesAsync(DeviceId); if (properties != null) { DeviceProperties = properties; } var propDescriptions = await ImuHubClient.GetDevicePropertyDescriptionsAsync(DeviceId); if (propDescriptions != null) { PropertyDescriptions = propDescriptions; } StateHasChanged(); } catch (Exception ex) { Snackbar.Add($"Failed to connect: {ex.Message}", Severity.Error); } } private async Task DisconnectAsync() { try { await ImuHubClient.StopAsync(); ImuData = new Imu(); // Reset về giá trị mặc định StateHasChanged(); } catch (Exception ex) { Snackbar.Add($"Failed to disconnect: {ex.Message}", Severity.Error); } } private async Task ReloadImuDataAsync() { if (!ImuHubClient.IsConnected || IsReloading) return; try { IsReloading = true; StateHasChanged(); ImuData = await ImuHubClient.ReadAllDataAsync(DeviceId); // Reload properties var properties = await ImuHubClient.GetDevicePropertiesAsync(DeviceId); if (properties != null) { DeviceProperties = properties; } } catch (Exception ex) { Snackbar.Add($"Failed to reload IMU data: {ex.Message}", Severity.Error); } finally { IsReloading = false; StateHasChanged(); } } public async ValueTask DisposeAsync() { await DisconnectAsync(); } /// /// Convert Quaternion sang Euler angles (Roll, Pitch, Yaw) /// private (double roll, double pitch, double yaw) QuaternionToEuler(QuaternionGeometry q) { // Roll (x-axis rotation) var sinr_cosp = 2 * (q.W * q.X + q.Y * q.Z); var cosr_cosp = 1 - 2 * (q.X * q.X + q.Y * q.Y); var roll = Math.Atan2(sinr_cosp, cosr_cosp); // Pitch (y-axis rotation) var sinp = 2 * (q.W * q.Y - q.Z * q.X); double pitch; if (Math.Abs(sinp) >= 1) pitch = Math.CopySign(Math.PI / 2, sinp); // use 90 degrees if out of range else pitch = Math.Asin(sinp); // Yaw (z-axis rotation) var siny_cosp = 2 * (q.W * q.Z + q.X * q.Y); var cosy_cosp = 1 - 2 * (q.Y * q.Y + q.Z * q.Z); var yaw = Math.Atan2(siny_cosp, cosy_cosp); return (roll, pitch, yaw); } /// /// Lấy danh sách properties đã sắp xếp để hiển thị /// private IEnumerable GetDisplayedProperties() { return PropertyDescriptions .OrderBy(p => p.DisplayOrder) .ThenBy(p => p.Category ?? "") .ThenBy(p => p.DisplayName); } /// /// Format giá trị property theo DataType và Format /// private string FormatPropertyValue(string value, PropertyDescription prop) { if (string.IsNullOrWhiteSpace(value)) return "-"; if (prop.DataType == "number" && double.TryParse(value, out var numValue)) { if (!string.IsNullOrWhiteSpace(prop.Format)) { try { return string.Format(prop.Format, numValue) + (prop.Unit != null ? $" {prop.Unit}" : ""); } catch { return value + (prop.Unit != null ? $" {prop.Unit}" : ""); } } return value + (prop.Unit != null ? $" {prop.Unit}" : ""); } return value + (prop.Unit != null ? $" {prop.Unit}" : ""); } }