@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();
}
}