Files
Denso/srcs/RobotNet10/RobotApp/RobotNet10.RobotApp.Client/Components/Devices/CiA402ServoCard.razor
2026-07-03 16:31:37 +07:00

1095 lines
53 KiB
Plaintext

@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
<MudCard>
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h5">@DeviceName</MudText>
<MudText Typo="Typo.body2" Color="Color.Secondary">@DeviceId</MudText>
</CardHeaderContent>
<CardHeaderActions>
<MudTooltip Text="Reload servo data">
<MudIconButton Icon="@Icons.Material.Filled.Refresh"
Color="Color.Primary"
Size="Size.Small"
OnClick="ReloadServoDataAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsReloading)">
</MudIconButton>
</MudTooltip>
</CardHeaderActions>
</MudCardHeader>
<MudCardContent Style="position: relative;" Class="pa-2">
<MudGrid Spacing="2">
<!-- Status & Connection -->
<MudItem xs="12">
<MudCard Elevation="0" Class="pa-2">
<MudGrid Spacing="1">
<MudItem xs="12" sm="3">
<MudChip T="string" Color="@(ServoData.IsConnected ? Color.Success : Color.Error)"
Size="Size.Small"
Variant="Variant.Filled">
@(ServoData.IsConnected ? "Connected" : "Disconnected")
</MudChip>
</MudItem>
<MudItem xs="12" sm="3">
<MudText Typo="Typo.body2">
<strong>State:</strong>
<MudChip T="string" Size="Size.Small" Variant="Variant.Text" Color="Color.Info">
@ServoData.DriveState
</MudChip>
</MudText>
</MudItem>
<MudItem xs="12" sm="3">
<MudText Typo="Typo.body2">
<strong>Mode:</strong>
<MudChip T="string" Size="Size.Small" Variant="Variant.Text" Color="Color.Success">
@ServoData.OperationMode
</MudChip>
</MudText>
</MudItem>
<MudItem xs="12" sm="3">
<MudText Typo="Typo.body2">
<strong>Error:</strong>
<MudChip T="string" Size="Size.Small"
Variant="Variant.Text"
Color="@(ServoData.ErrorCode == 0 ? Color.Success : Color.Error)">
0x@(ServoData.ErrorCode.ToString("X4"))
</MudChip>
</MudText>
</MudItem>
<MudItem xs="12">
<MudText Typo="Typo.body2">
<strong>Statusword:</strong> 0x@(ServoData.Statusword.ToString("X4"))
</MudText>
</MudItem>
</MudGrid>
</MudCard>
</MudItem>
<!-- Position, Velocity, Torque -->
<MudItem xs="12">
<MudCard Elevation="0" Class="pa-2">
<MudGrid Spacing="1">
<MudItem xs="12" sm="4">
<MudText Typo="Typo.body2">
<strong>Position:</strong>
</MudText>
<MudText Typo="Typo.body1" Color="Color.Primary">
@ServoData.Position.ToString("N0")
</MudText>
<MudText Typo="Typo.caption" Color="Color.Secondary">counts</MudText>
</MudItem>
<MudItem xs="12" sm="4">
<MudText Typo="Typo.body2">
<strong>Velocity:</strong>
</MudText>
<MudText Typo="Typo.body1" Color="@GetVelocityColor()">
@ServoData.Velocity.ToString("N0")
</MudText>
<MudText Typo="Typo.caption" Color="Color.Secondary">counts/s</MudText>
</MudItem>
<MudItem xs="12" sm="4">
<MudText Typo="Typo.body2">
<strong>Torque:</strong>
</MudText>
<MudText Typo="Typo.body1" Color="Color.Warning">
@ServoData.Torque.ToString("N0")
</MudText>
<MudText Typo="Typo.caption" Color="Color.Secondary">units</MudText>
</MudItem>
</MudGrid>
</MudCard>
</MudItem>
<!-- Controls Section -->
<MudItem xs="12">
<MudCard Elevation="0" Class="pa-2">
<!-- State Machine Controls -->
<MudText Typo="Typo.subtitle2" Color="Color.Primary" Class="mb-1">State Control</MudText>
<MudButtonGroup OverrideStyles="false" DropShadow>
<MudButton Variant="Variant.Filled"
Color="Color.Success"
Size="Size.Small"
FullWidth="true"
StartIcon="@Icons.Material.Filled.PlayArrow"
OnClick="HandleEnableAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Enable
</MudButton>
<MudButton Variant="Variant.Filled"
Color="Color.Error"
Size="Size.Small"
FullWidth="true"
StartIcon="@Icons.Material.Filled.Stop"
OnClick="HandleDisableAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Disable
</MudButton>
<MudButton Variant="Variant.Filled"
Color="Color.Warning"
Size="Size.Small"
FullWidth="true"
StartIcon="@Icons.Material.Filled.Emergency"
OnClick="HandleQuickStopAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Quick Stop
</MudButton>
<MudButton Variant="Variant.Filled"
Color="Color.Info"
Size="Size.Small"
FullWidth="true"
StartIcon="@Icons.Material.Filled.Refresh"
OnClick="HandleFaultResetAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Reset
</MudButton>
</MudButtonGroup>
<!-- Advanced State Machine Controls -->
<MudExpansionPanels Elevation="0" Class="mt-2">
<MudExpansionPanel Text="Advanced State Control" Icon="@Icons.Material.Filled.Settings">
<MudGrid Spacing="1">
<MudItem xs="6" sm="3">
<MudButton Variant="Variant.Outlined"
Color="Color.Primary"
Size="Size.Small"
FullWidth="true"
StartIcon="@Icons.Material.Filled.PowerOff"
OnClick="HandleShutdownAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Shutdown
</MudButton>
</MudItem>
<MudItem xs="6" sm="3">
<MudButton Variant="Variant.Outlined"
Color="Color.Primary"
Size="Size.Small"
FullWidth="true"
StartIcon="@Icons.Material.Filled.Power"
OnClick="HandleSwitchOnAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Switch On
</MudButton>
</MudItem>
<MudItem xs="6" sm="3">
<MudButton Variant="Variant.Outlined"
Color="Color.Success"
Size="Size.Small"
FullWidth="true"
StartIcon="@Icons.Material.Filled.PlayCircle"
OnClick="HandleEnableOperationAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Enable Op
</MudButton>
</MudItem>
<MudItem xs="6" sm="3">
<MudButton Variant="Variant.Outlined"
Color="Color.Error"
Size="Size.Small"
FullWidth="true"
StartIcon="@Icons.Material.Filled.PauseCircle"
OnClick="HandleDisableOperationAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Disable Op
</MudButton>
</MudItem>
</MudGrid>
</MudExpansionPanel>
</MudExpansionPanels>
<!-- Operation Mode -->
<MudStack Row AlignItems="@AlignItems.Center" Class="mt-2">
<MudSelect T="string"
Label="Operation Mode"
ShrinkLabel
Dense Margin="@Margin.Dense"
Variant="Variant.Outlined"
Value="@SelectedOperationMode"
ValueChanged="OnOperationModeChanged"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
<MudSelectItem Value="@("ProfilePosition")">Profile Position</MudSelectItem>
<MudSelectItem Value="@("ProfileVelocity")">Profile Velocity</MudSelectItem>
<MudSelectItem Value="@("ProfileTorque")">Profile Torque</MudSelectItem>
<MudSelectItem Value="@("Homing")">Homing</MudSelectItem>
<MudSelectItem Value="@("NoMode")">No Mode</MudSelectItem>
</MudSelect>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
Size="Size.Small"
OnClick="HandleSetOperationModeAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting || string.IsNullOrEmpty(SelectedOperationMode))">
Set
</MudButton>
</MudStack>
<!-- Position Control -->
<MudText Typo="Typo.subtitle2" Color="Color.Primary" Class="mb-1 mt-2">Position Control</MudText>
<MudGrid Spacing="1" Class="mb-2">
<MudItem xs="12" sm="6">
<MudNumericField T="int"
Label="Target Position"
Variant="Variant.Outlined"
Margin="Margin.Dense"
@bind-Value="@TargetPosition"
HelperText="counts (can be negative)"
Min="@int.MinValue"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)" />
</MudItem>
<MudItem xs="6" sm="2">
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
Size="Size.Small"
FullWidth="true"
StartIcon="@Icons.Material.Filled.LocationOn"
OnClick="HandleSetTargetPositionAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Set
</MudButton>
</MudItem>
<MudItem xs="6" sm="2">
<MudButton Variant="Variant.Filled"
Color="Color.Success"
Size="Size.Small"
FullWidth="true"
StartIcon="@Icons.Material.Filled.Navigation"
OnClick="HandleMoveToPositionAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Move
</MudButton>
</MudItem>
<MudItem xs="6" sm="2">
<MudButton Variant="Variant.Outlined"
Color="Color.Info"
Size="Size.Small"
FullWidth="true"
StartIcon="@Icons.Material.Filled.PlayArrow"
OnClick="HandleStartPositionMoveAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Start
</MudButton>
</MudItem>
</MudGrid>
<!-- Wait Until At Target -->
<MudGrid Spacing="1" Class="mb-2">
<MudItem xs="12" sm="6">
<MudNumericField T="int"
Label="Tolerance"
Variant="Variant.Outlined"
Margin="Margin.Dense"
@bind-Value="@PositionTolerance"
HelperText="counts (can be negative)"
Min="@int.MinValue"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)" />
</MudItem>
<MudItem xs="12" sm="3">
<MudCheckBox T="bool"
Label="Use Statusword"
@bind-Value="@UseStatuswordForTarget"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)" />
</MudItem>
<MudItem xs="12" sm="3">
<MudButton Variant="Variant.Outlined"
Color="Color.Success"
Size="Size.Small"
FullWidth="true"
StartIcon="@Icons.Material.Filled.HourglassEmpty"
OnClick="HandleWaitUntilAtTargetAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Wait Target
</MudButton>
</MudItem>
</MudGrid>
<!-- Profile Settings for Position -->
<MudExpansionPanels Elevation="0" Class="mb-2">
<MudExpansionPanel Text="Profile Settings" Icon="@Icons.Material.Filled.Tune">
<MudGrid Spacing="1">
<MudItem xs="12" sm="4">
<MudNumericField T="uint"
Label="Profile Speed"
Variant="Variant.Outlined"
Margin="Margin.Dense"
@bind-Value="@ProfileSpeed"
HelperText="counts/s"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)" />
</MudItem>
<MudItem xs="12" sm="4">
<MudNumericField T="uint"
Label="Acceleration"
Variant="Variant.Outlined"
Margin="Margin.Dense"
@bind-Value="@ProfileAcceleration"
HelperText="counts/s²"
Min="0"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)" />
</MudItem>
<MudItem xs="12" sm="4">
<MudNumericField T="uint"
Label="Deceleration"
Variant="Variant.Outlined"
Margin="Margin.Dense"
@bind-Value="@ProfileDeceleration"
HelperText="counts/s²"
Min="0"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)" />
</MudItem>
<MudItem xs="12" sm="4">
<MudButton Variant="Variant.Outlined"
Color="Color.Primary"
Size="Size.Small"
FullWidth="true"
OnClick="HandleSetProfileSpeedAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Set Speed
</MudButton>
</MudItem>
<MudItem xs="12" sm="4">
<MudButton Variant="Variant.Outlined"
Color="Color.Primary"
Size="Size.Small"
FullWidth="true"
OnClick="HandleSetProfileAccelerationAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Set Accel
</MudButton>
</MudItem>
<MudItem xs="12" sm="4">
<MudButton Variant="Variant.Outlined"
Color="Color.Primary"
Size="Size.Small"
FullWidth="true"
OnClick="HandleSetProfileDecelerationAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Set Decel
</MudButton>
</MudItem>
<MudItem xs="12">
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
Size="Size.Small"
FullWidth="true"
OnClick="HandleSetAllProfileAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Set All Profile (write Speed + Accel + Decel to drive)
</MudButton>
</MudItem>
</MudGrid>
</MudExpansionPanel>
</MudExpansionPanels>
<!-- Velocity & Torque Controls -->
<MudGrid Spacing="1">
<MudItem xs="12" sm="6">
<MudText Typo="Typo.subtitle2" Color="Color.Info" Class="mb-1">Velocity</MudText>
<MudGrid Spacing="1">
<MudItem xs="12" sm="8">
<MudNumericField T="int"
Label="Target Velocity"
Variant="Variant.Outlined"
Margin="Margin.Dense"
@bind-Value="@TargetVelocity"
HelperText="counts/s (can be negative)"
Min="@int.MinValue"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)" />
</MudItem>
<MudItem xs="12" sm="4">
<MudButton Variant="Variant.Filled"
Color="Color.Info"
Size="Size.Small"
FullWidth="true"
StartIcon="@Icons.Material.Filled.Speed"
OnClick="HandleSetTargetVelocityAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Set
</MudButton>
</MudItem>
<MudItem xs="12" sm="6">
<MudButton Variant="Variant.Outlined"
Color="Color.Info"
Size="Size.Small"
FullWidth="true"
StartIcon="@Icons.Material.Filled.PlayArrow"
OnClick="HandleTargetVelocityAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Target Vel
</MudButton>
</MudItem>
<MudItem xs="12" sm="6">
<MudButton Variant="Variant.Outlined"
Color="Color.Info"
Size="Size.Small"
FullWidth="true"
StartIcon="@Icons.Material.Filled.Speed"
OnClick="HandleProfileVelocityAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Profile Vel
</MudButton>
</MudItem>
</MudGrid>
</MudItem>
<MudItem xs="12" sm="6">
<MudText Typo="Typo.subtitle2" Color="Color.Warning" Class="mb-1">Torque</MudText>
<MudGrid Spacing="1">
<MudItem xs="12" sm="8">
<MudNumericField T="short"
Label="Target Torque"
Variant="Variant.Outlined"
Margin="Margin.Dense"
@bind-Value="@TargetTorque"
HelperText="units (can be negative)"
Min="@short.MinValue"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)" />
</MudItem>
<MudItem xs="12" sm="4">
<MudButton Variant="Variant.Filled"
Color="Color.Warning"
Size="Size.Small"
FullWidth="true"
StartIcon="@Icons.Material.Filled.Power"
OnClick="HandleSetTargetTorqueAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Set
</MudButton>
</MudItem>
<MudItem xs="12">
<MudButton Variant="Variant.Outlined"
Color="Color.Warning"
Size="Size.Small"
FullWidth="true"
StartIcon="@Icons.Material.Filled.PlayArrow"
OnClick="HandleRunTorqueAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Run Torque
</MudButton>
</MudItem>
</MudGrid>
</MudItem>
</MudGrid>
<!-- Homing Control -->
<MudText Typo="Typo.subtitle2" Color="Color.Secondary" Class="mb-1 mt-2">Homing</MudText>
<MudGrid Spacing="1" Class="mb-2">
<MudItem xs="12" sm="4">
<MudNumericField T="byte"
Label="Homing Method"
Variant="Variant.Outlined"
Margin="Margin.Dense"
@bind-Value="@HomingMethod"
HelperText="Method number "
Min="1"
Max="254"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)" />
</MudItem>
<MudItem xs="12" sm="4">
<MudNumericField T="int"
Label="Homing Speed"
Variant="Variant.Outlined"
Margin="Margin.Dense"
@bind-Value="@HomingSpeed"
HelperText="counts/s (can be negative)"
Min="@int.MinValue"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)" />
</MudItem>
<MudItem xs="12" sm="4">
<MudNumericField T="int"
Label="Homing Offset"
Variant="Variant.Outlined"
Margin="Margin.Dense"
@bind-Value="@HomingOffset"
HelperText="counts (can be negative)"
Min="@int.MinValue"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)" />
</MudItem>
<MudItem xs="6" sm="6">
<MudButton Variant="Variant.Outlined"
Color="Color.Secondary"
Size="Size.Small"
FullWidth="true"
OnClick="HandleApplyHomingParamsAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Apply Params
</MudButton>
</MudItem>
<MudItem xs="6" sm="6">
<MudButton Variant="Variant.Filled"
Color="Color.Secondary"
Size="Size.Small"
FullWidth="true"
StartIcon="@Icons.Material.Filled.Home"
OnClick="HandleStartHomingAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Start
</MudButton>
</MudItem>
</MudGrid>
<!-- Error Information -->
<MudExpansionPanels Elevation="0" Class="mb-2">
<MudExpansionPanel Text="Error Information" Icon="@Icons.Material.Filled.Error">
<MudGrid Spacing="1">
<MudItem xs="12">
<MudButton Variant="Variant.Outlined"
Color="Color.Error"
Size="Size.Small"
StartIcon="@Icons.Material.Filled.Refresh"
OnClick="HandleRefreshErrorInfoAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Refresh Error Info
</MudButton>
</MudItem>
<MudItem xs="12" sm="6">
<MudText Typo="Typo.body2">
<strong>Error Register:</strong> 0x@(ErrorRegister.ToString("X2"))
</MudText>
</MudItem>
<MudItem xs="12" sm="6">
<MudText Typo="Typo.body2">
<strong>Latest Error:</strong> 0x@(LatestErrorCode.ToString("X4"))
</MudText>
</MudItem>
<MudItem xs="12">
<MudText Typo="Typo.body2">
<strong>Error History:</strong>
</MudText>
@if (ErrorHistory != null && ErrorHistory.Length > 0)
{
<MudChipSet T="ushort">
@foreach (var error in ErrorHistory)
{
<MudChip T="ushort"
Color="Color.Error"
Size="Size.Small"
Variant="Variant.Text">
0x@(error.ToString("X4"))
</MudChip>
}
</MudChipSet>
}
else
{
<MudText Typo="Typo.caption" Color="Color.Secondary">No errors</MudText>
}
</MudItem>
</MudGrid>
</MudExpansionPanel>
</MudExpansionPanels>
<!-- Status Information -->
<MudExpansionPanels Elevation="0" Class="mb-2">
<MudExpansionPanel Text="Status Information" Icon="@Icons.Material.Filled.Info">
<MudGrid Spacing="1">
<MudItem xs="12">
<MudButton Variant="Variant.Outlined"
Color="Color.Info"
Size="Size.Small"
StartIcon="@Icons.Material.Filled.Refresh"
OnClick="HandleRefreshStatusInfoAsync"
Disabled="@(!CiA402ServoHubClient.IsConnected || IsActionExecuting)">
Refresh Status
</MudButton>
</MudItem>
<MudItem xs="12" sm="4">
<MudChip T="bool"
Color="@(IsInFault ? Color.Error : Color.Success)"
Size="Size.Small"
Variant="Variant.Filled">
@(IsInFault ? "In Fault" : "No Fault")
</MudChip>
</MudItem>
<MudItem xs="12" sm="4">
<MudChip T="bool"
Color="@(IsEnabled ? Color.Success : Color.Default)"
Size="Size.Small"
Variant="Variant.Filled">
@(IsEnabled ? "Enabled" : "Disabled")
</MudChip>
</MudItem>
<MudItem xs="12" sm="4">
<MudChip T="bool"
Color="@(IsReady ? Color.Success : Color.Default)"
Size="Size.Small"
Variant="Variant.Filled">
@(IsReady ? "Ready" : "Not Ready")
</MudChip>
</MudItem>
</MudGrid>
</MudExpansionPanel>
</MudExpansionPanels>
</MudCard>
</MudItem>
</MudGrid>
</MudCardContent>
</MudCard>
<MudOverlay Visible="@IsLoading" Absolute="true">
<MudProgressCircular Indeterminate="true" Size="Size.Large" />
</MudOverlay>
@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<Task> 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();
}
}