@implements IAsyncDisposable @using MudBlazor @using Microsoft.AspNetCore.SignalR.Client @using RobotNet10.RobotApp.Client.Clients @using RobotNet10.RobotApp.Client.Shared.Devices @inject CiA402ServoHubClient CiA402ServoHubClient @inject ISnackbar Snackbar @DeviceName @DeviceId @(ServoData.IsConnected ? "Connected" : "Disconnected") State: @ServoData.DriveState Mode: @ServoData.OperationMode Error: 0x@(ServoData.ErrorCode.ToString("X4")) Statusword: 0x@(ServoData.Statusword.ToString("X4")) Position: @ServoData.Position.ToString("N0") counts Velocity: @ServoData.Velocity.ToString("N0") counts/s Torque: @ServoData.Torque.ToString("N0") units State Control Enable Disable Quick Stop Reset Shutdown Switch On Enable Op Disable Op Profile Position Profile Velocity Profile Torque Homing No Mode Set Position Control Set Move Start Wait Target Set Speed Set Accel Set Decel Set All Profile (write Speed + Accel + Decel to drive) Velocity Set Target Vel Profile Vel Torque Set Run Torque Homing Apply Params Start Refresh Error Info Error Register: 0x@(ErrorRegister.ToString("X2")) Latest Error: 0x@(LatestErrorCode.ToString("X4")) Error History: @if (ErrorHistory != null && ErrorHistory.Length > 0) { @foreach (var error in ErrorHistory) { 0x@(error.ToString("X4")) } } else { No errors } Refresh Status @(IsInFault ? "In Fault" : "No Fault") @(IsEnabled ? "Enabled" : "Disabled") @(IsReady ? "Ready" : "Not Ready") @code { [Parameter, EditorRequired] public string DeviceId { get; set; } = string.Empty; private string DeviceName { get; set; } = string.Empty; private CiA402ServoDataDto ServoData = new(); private bool IsLoading => !CiA402ServoHubClient.IsConnected; private bool IsReloading = false; private bool IsActionExecuting = false; // Control values private string SelectedOperationMode { get; set; } = string.Empty; private int TargetPosition { get; set; } = 0; private int TargetVelocity { get; set; } = 0; private short TargetTorque { get; set; } = 0; // Profile Settings private uint ProfileSpeed { get; set; } = 5000; private uint ProfileAcceleration { get; set; } = 5000; private uint ProfileDeceleration { get; set; } = 5000; // Homing private byte HomingMethod { get; set; } = 21; private int HomingSpeed { get; set; } = 5000; private int HomingOffset { get; set; } = 0; // Error Information private byte ErrorRegister { get; set; } = 0; private ushort LatestErrorCode { get; set; } = 0; private ushort[] ErrorHistory { get; set; } = []; // Status Information private bool IsInFault { get; set; } = false; private bool IsEnabled { get; set; } = false; private bool IsReady { get; set; } = false; // Wait Until At Target private int PositionTolerance { get; set; } = 100; private bool UseStatuswordForTarget { get; set; } = true; 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 CiA402ServoHubClient.StartAsync(); var deviceInfo = await CiA402ServoHubClient.GetDeviceInfoAsync(DeviceId); if (deviceInfo != null) { DeviceName = deviceInfo.DeviceName; } else { DeviceName = DeviceId; } ServoData = await CiA402ServoHubClient.GetServoDataAsync(DeviceId); SyncProfileFromServoData(); StateHasChanged(); } catch (Exception ex) { Snackbar.Add($"Failed to connect: {ex.Message}", Severity.Error); } } private async Task DisconnectAsync() { try { await CiA402ServoHubClient.StopAsync(); ServoData = new CiA402ServoDataDto(); StateHasChanged(); } catch (Exception ex) { Snackbar.Add($"Failed to disconnect: {ex.Message}", Severity.Error); } } private async Task ReloadServoDataAsync() { if (!CiA402ServoHubClient.IsConnected || IsReloading) return; try { IsReloading = true; StateHasChanged(); ServoData = await CiA402ServoHubClient.GetServoDataAsync(DeviceId); SyncProfileFromServoData(); } catch (Exception ex) { Snackbar.Add($"Failed to reload servo data: {ex.Message}", Severity.Error); } finally { IsReloading = false; StateHasChanged(); } } private void SyncProfileFromServoData() { ProfileSpeed = ServoData.ProfileSpeed; ProfileAcceleration = ServoData.ProfileAcceleration; ProfileDeceleration = ServoData.ProfileDeceleration; } private Color GetVelocityColor() { var absVelocity = Math.Abs(ServoData.Velocity); if (absVelocity < 1) { return Color.Default; } else if (absVelocity < 100) { return Color.Info; } else if (absVelocity < 1000) { return Color.Success; } else { return Color.Warning; } } // State Machine Control Handlers private async Task HandleEnableAsync() { await ExecuteActionAsync(async () => { await CiA402ServoHubClient.EnableAsync(DeviceId); await ReloadServoDataAsync(); }); } private async Task HandleDisableAsync() { await ExecuteActionAsync(async () => { await CiA402ServoHubClient.DisableAsync(DeviceId); await ReloadServoDataAsync(); }); } private async Task HandleQuickStopAsync() { await ExecuteActionAsync(async () => { await CiA402ServoHubClient.QuickStopAsync(DeviceId); await ReloadServoDataAsync(); }); } private async Task HandleFaultResetAsync() { await ExecuteActionAsync(async () => { await CiA402ServoHubClient.FaultResetAsync(DeviceId); await ReloadServoDataAsync(); }); } // Operation Mode Handler private void OnOperationModeChanged(string? value) { SelectedOperationMode = value ?? string.Empty; } private async Task HandleSetOperationModeAsync() { if (string.IsNullOrEmpty(SelectedOperationMode)) return; await ExecuteActionAsync(async () => { await CiA402ServoHubClient.SetOperationModeAsync(DeviceId, SelectedOperationMode); await ReloadServoDataAsync(); }); } // Position Control Handlers private async Task HandleSetTargetPositionAsync() { await ExecuteActionAsync(async () => { await CiA402ServoHubClient.SetTargetPositionAsync(DeviceId, TargetPosition); await ReloadServoDataAsync(); }); } private async Task HandleMoveToPositionAsync() { await ExecuteActionAsync(async () => { await CiA402ServoHubClient.MoveToPositionAsync(DeviceId, TargetPosition); await ReloadServoDataAsync(); }); } // Velocity Control Handler private async Task HandleSetTargetVelocityAsync() { await ExecuteActionAsync(async () => { await CiA402ServoHubClient.SetTargetVelocityAsync(DeviceId, TargetVelocity); await ReloadServoDataAsync(); }); } // Torque Control Handler private async Task HandleSetTargetTorqueAsync() { await ExecuteActionAsync(async () => { await CiA402ServoHubClient.SetTargetTorqueAsync(DeviceId, TargetTorque); await ReloadServoDataAsync(); }); } private async Task HandleRunTorqueAsync() { await ExecuteActionAsync(async () => { await CiA402ServoHubClient.RunTorqueAsync(DeviceId, TargetTorque); await ReloadServoDataAsync(); }); } // Velocity Control - Additional Methods private async Task HandleTargetVelocityAsync() { await ExecuteActionAsync(async () => { await CiA402ServoHubClient.TargetVelocityAsync(DeviceId, TargetVelocity, ProfileAcceleration, ProfileDeceleration); await ReloadServoDataAsync(); }); } private async Task HandleProfileVelocityAsync() { await ExecuteActionAsync(async () => { await CiA402ServoHubClient.ProfileVelocityAsync(DeviceId, TargetVelocity, ProfileAcceleration, ProfileDeceleration); await ReloadServoDataAsync(); }); } // Profile Settings Handlers private async Task HandleSetProfileSpeedAsync() { await ExecuteActionAsync(async () => { await CiA402ServoHubClient.SetProfileSpeedAsync(DeviceId, ProfileSpeed); await ReloadServoDataAsync(); }); } private async Task HandleSetProfileAccelerationAsync() { await ExecuteActionAsync(async () => { await CiA402ServoHubClient.SetProfileAccelerationAsync(DeviceId, ProfileAcceleration); await ReloadServoDataAsync(); }); } private async Task HandleSetProfileDecelerationAsync() { await ExecuteActionAsync(async () => { await CiA402ServoHubClient.SetProfileDecelerationAsync(DeviceId, ProfileDeceleration); await ReloadServoDataAsync(); }); } private async Task HandleSetAllProfileAsync() { await ExecuteActionAsync(async () => { await CiA402ServoHubClient.SetProfileSettingsAsync(DeviceId, ProfileSpeed, ProfileAcceleration, ProfileDeceleration); await ReloadServoDataAsync(); }); } // Position Control - Additional Methods private async Task HandleStartPositionMoveAsync() { await ExecuteActionAsync(async () => { await CiA402ServoHubClient.StartPositionMoveAsync(DeviceId); await ReloadServoDataAsync(); }); } // Homing Handlers private async Task HandleApplyHomingParamsAsync() { await ExecuteActionAsync(async () => { await CiA402ServoHubClient.SetHomingMethodAsync(DeviceId, HomingMethod); await CiA402ServoHubClient.SetHomingSpeedAsync(DeviceId, HomingSpeed); await CiA402ServoHubClient.SetHomingOffsetAsync(DeviceId, HomingOffset); await ReloadServoDataAsync(); // Read back from drive to verify settings were applied var readMethod = await CiA402ServoHubClient.GetHomingMethodAsync(DeviceId); var readSpeed = await CiA402ServoHubClient.GetHomingSpeedAsync(DeviceId); var readOffset = await CiA402ServoHubClient.GetHomingOffsetAsync(DeviceId); Snackbar.Add($"Applied to drive — Method: {readMethod}, Speed: {readSpeed}, Offset: {readOffset}", Severity.Success); }); } private async Task HandleStartHomingAsync() { await ExecuteActionAsync(async () => { await CiA402ServoHubClient.SetHomingMethodAsync(DeviceId, HomingMethod); await CiA402ServoHubClient.SetHomingSpeedAsync(DeviceId, HomingSpeed); await CiA402ServoHubClient.SetHomingOffsetAsync(DeviceId, HomingOffset); await CiA402ServoHubClient.StartHomingAsync(DeviceId, HomingMethod, HomingSpeed); await ReloadServoDataAsync(); // Read back from drive to verify settings were applied var readMethod = await CiA402ServoHubClient.GetHomingMethodAsync(DeviceId); var readSpeed = await CiA402ServoHubClient.GetHomingSpeedAsync(DeviceId); var readOffset = await CiA402ServoHubClient.GetHomingOffsetAsync(DeviceId); Snackbar.Add($"Homing started. Drive values — Method: {readMethod}, Speed: {readSpeed}, Offset: {readOffset}", Severity.Success); }); } // Advanced State Machine Control Handlers private async Task HandleShutdownAsync() { await ExecuteActionAsync(async () => { await CiA402ServoHubClient.ShutdownAsync(DeviceId); await ReloadServoDataAsync(); }); } private async Task HandleSwitchOnAsync() { await ExecuteActionAsync(async () => { await CiA402ServoHubClient.SwitchOnAsync(DeviceId); await ReloadServoDataAsync(); }); } private async Task HandleEnableOperationAsync() { await ExecuteActionAsync(async () => { await CiA402ServoHubClient.EnableOperationAsync(DeviceId); await ReloadServoDataAsync(); }); } private async Task HandleDisableOperationAsync() { await ExecuteActionAsync(async () => { await CiA402ServoHubClient.DisableOperationAsync(DeviceId); await ReloadServoDataAsync(); }); } // Error Information Handlers private async Task HandleRefreshErrorInfoAsync() { await ExecuteActionAsync(async () => { ErrorRegister = await CiA402ServoHubClient.GetErrorRegisterAsync(DeviceId); LatestErrorCode = await CiA402ServoHubClient.GetLatestErrorCodeAsync(DeviceId); ErrorHistory = await CiA402ServoHubClient.GetErrorHistoryAsync(DeviceId); StateHasChanged(); }); } // Status Information Handlers private async Task HandleRefreshStatusInfoAsync() { await ExecuteActionAsync(async () => { IsInFault = await CiA402ServoHubClient.IsInFaultStateAsync(DeviceId); IsEnabled = await CiA402ServoHubClient.IsEnabledAsync(DeviceId); IsReady = await CiA402ServoHubClient.IsReadyAsync(DeviceId); StateHasChanged(); }); } // Wait Until At Target Handler private async Task HandleWaitUntilAtTargetAsync() { await ExecuteActionAsync(async () => { try { Snackbar.Add("Waiting for servo to reach target position...", Severity.Info); await CiA402ServoHubClient.WaitUntilAtTargetAsync(DeviceId, PositionTolerance, UseStatuswordForTarget); Snackbar.Add("Servo reached target position!", Severity.Success); await ReloadServoDataAsync(); } catch (OperationCanceledException) { Snackbar.Add("Wait for target was cancelled", Severity.Warning); } }); } // Helper method để execute action với error handling private async Task ExecuteActionAsync(Func action) { if (!CiA402ServoHubClient.IsConnected || IsActionExecuting) return; try { IsActionExecuting = true; StateHasChanged(); await action(); } catch (Exception ex) { Snackbar.Add($"Action failed: {ex.Message}", Severity.Error); } finally { IsActionExecuting = false; StateHasChanged(); } } public async ValueTask DisposeAsync() { await DisconnectAsync(); } }