360 lines
14 KiB
Plaintext
360 lines
14 KiB
Plaintext
@implements IAsyncDisposable
|
|
|
|
@using MudBlazor
|
|
@using RobotNet10.RobotApp.Client.Clients
|
|
@using RobotNet10.RobotApp.Client.Shared.Devices
|
|
|
|
@inject RfHandleHubClient RfHandleHubClient
|
|
@inject ISnackbar Snackbar
|
|
|
|
<MudCard Class="pa-0">
|
|
<MudCardHeader Class="pb-0">
|
|
<CardHeaderContent>
|
|
<MudText Typo="Typo.h5">@DeviceName</MudText>
|
|
<MudText Typo="Typo.caption">@DeviceId</MudText>
|
|
</CardHeaderContent>
|
|
<CardHeaderActions>
|
|
<MudTooltip Text="Toggle auto reload (1s interval)">
|
|
<MudIconButton Icon="@(AutoReloadEnabled? Icons.Material.Filled.PauseCircle : Icons.Material.Filled.PlayCircle)"
|
|
Color="@(AutoReloadEnabled ? Color.Success : Color.Default)"
|
|
Size="Size.Small"
|
|
OnClick="ToggleAutoReloadAsync"
|
|
Disabled="@(!RfHandleHubClient.IsConnected)">
|
|
</MudIconButton>
|
|
</MudTooltip>
|
|
<MudTooltip Text="Reload RfHandle data">
|
|
<MudIconButton Icon="@Icons.Material.Filled.Refresh"
|
|
Size="Size.Small"
|
|
Color="Color.Primary"
|
|
Disabled="@(!RfHandleHubClient.IsConnected || IsReloading)"
|
|
OnClick="ReloadRfHandleDataAsync" />
|
|
</MudTooltip>
|
|
</CardHeaderActions>
|
|
</MudCardHeader>
|
|
|
|
<MudCardContent Class="pa-3">
|
|
|
|
<MudGrid>
|
|
|
|
<!-- LEFT PANEL (NO JOYSTICK FOR YNZDH) -->
|
|
<MudItem xs="12" md="5">
|
|
<MudPaper Elevation="1" Class="pa-3 d-flex flex-column">
|
|
<MudText Typo="Typo.subtitle2" Color="Color.Primary">Joystick</MudText>
|
|
<MudItem xs="12">
|
|
<!-- ================= LEFT: JOYSTICK ================= -->
|
|
<svg width="400" height="400" viewBox="0 0 220 220">
|
|
<!-- Background -->
|
|
<circle cx="110" cy="110" r="95" fill="#2e2e2e" />
|
|
|
|
<!-- Deadzone -->
|
|
<circle cx="110" cy="110" r="25" fill="#3f3f3f" />
|
|
|
|
<!-- Axis cross -->
|
|
<line x1="110" y1="15" x2="110" y2="205"
|
|
stroke="#444" stroke-width="2" />
|
|
<line x1="15" y1="110" x2="205" y2="110"
|
|
stroke="#444" stroke-width="2" />
|
|
|
|
<!-- Joystick knob -->
|
|
<circle cx="@JoyX"
|
|
cy="@JoyY"
|
|
r="28"
|
|
fill="@JoyColor"
|
|
stroke="#111"
|
|
stroke-width="3" />
|
|
</svg>
|
|
<!-- ================= RIGHT: AXES ================= -->
|
|
<!-- LINEAR -->
|
|
<MudText Typo="Typo.subtitle2" Color="Color.Primary">
|
|
Linear
|
|
</MudText>
|
|
|
|
<MudProgressLinear Value="@AxisToPercent(RfHandleData.Linear)"
|
|
Color="Color.Info"
|
|
Class="mb-1" />
|
|
|
|
<MudText Typo="Typo.caption" Class="mb-3">
|
|
@AxisText(RfHandleData.Linear)
|
|
</MudText>
|
|
|
|
<!-- ANGULAR -->
|
|
<MudText Typo="Typo.subtitle2" Color="Color.Primary">
|
|
Angular
|
|
</MudText>
|
|
|
|
<MudProgressLinear Value="@AxisToPercent(RfHandleData.Angular)"
|
|
Color="Color.Info"
|
|
Class="mb-1" />
|
|
|
|
<MudText Typo="Typo.caption">
|
|
@AxisText(RfHandleData.Angular)
|
|
</MudText>
|
|
|
|
</MudItem>
|
|
|
|
</MudPaper>
|
|
</MudItem>
|
|
|
|
<!-- RIGHT PANELS -->
|
|
<MudItem xs="12" md="7">
|
|
<MudGrid Spacing="2">
|
|
|
|
<!-- STATUS PANEL -->
|
|
<MudItem xs="12">
|
|
<MudPaper Elevation="1" Class="pa-2">
|
|
<MudText Typo="Typo.subtitle2" Color="Color.Info">Status</MudText>
|
|
|
|
<MudStack Row="true" Spacing="2" Class="mt-2">
|
|
<MudChip T="string" Color="@(RfHandleData.Heartbeat != 0 ? Color.Success : Color.Default)"
|
|
Variant="@(RfHandleData.Heartbeat != 0 ? Variant.Filled : Variant.Outlined)"
|
|
Size="Size.Small">Heartbeat</MudChip>
|
|
<MudChip T="string" Color="@(RfHandleData.RemoteReady? Color.Warning: Color.Default)"
|
|
Variant="@(RfHandleData.RemoteReady ? Variant.Filled : Variant.Outlined)"
|
|
Size="Size.Small">RemoteReady</MudChip>
|
|
<MudChip T="string" Color="@(RfHandleData.EStop? Color.Error: Color.Default)"
|
|
Variant="@(RfHandleData.EStop ? Variant.Filled : Variant.Outlined)"
|
|
Size="Size.Small">EStop</MudChip>
|
|
</MudStack>
|
|
|
|
<MudText Class="mt-2">
|
|
<b>Last Update:</b> @RfHandleData.LastUpdateTime.ToString("HH:mm:ss")
|
|
</MudText>
|
|
</MudPaper>
|
|
</MudItem>
|
|
|
|
<!-- SPEED PANEL -->
|
|
<MudItem xs="12">
|
|
<MudPaper Elevation="1" Class="pa-2">
|
|
<MudText Typo="Typo.subtitle2" Color="Color.Primary">Speed</MudText>
|
|
|
|
<MudProgressLinear Value="@RfHandleData.Speed" Color="Color.Info" Class="mt-2" />
|
|
<MudText Typo="Typo.caption">Speed: @RfHandleData.Speed%</MudText>
|
|
</MudPaper>
|
|
</MudItem>
|
|
|
|
<!-- MOTION PANEL -->
|
|
<MudItem xs="12">
|
|
<MudPaper Elevation="1" Class="pa-2">
|
|
<MudText Typo="Typo.subtitle2" Color="Color.Warning">Motion</MudText>
|
|
|
|
<MudStack Row="true" Spacing="2" Class="mt-2">
|
|
<MudChip T="string" Color="@(RfHandleData.LiftUp? Color.Info: Color.Default)"
|
|
Variant="@(RfHandleData.LiftUp ? Variant.Filled : Variant.Outlined)"
|
|
Size="Size.Small">LiftUp</MudChip>
|
|
<MudChip T="string" Color="@(RfHandleData.LiftDown? Color.Info: Color.Default)"
|
|
Variant="@(RfHandleData.LiftDown ? Variant.Filled : Variant.Outlined)"
|
|
Size="Size.Small">LiftDown</MudChip>
|
|
|
|
<MudChip T="string" Color="@(RfHandleData.RotateLeft? Color.Secondary: Color.Default)"
|
|
Variant="@(RfHandleData.RotateLeft ? Variant.Filled : Variant.Outlined)"
|
|
Size="Size.Small">Rot L</MudChip>
|
|
<MudChip T="string" Color="@(RfHandleData.RotateRight? Color.Secondary: Color.Default)"
|
|
Variant="@(RfHandleData.RotateRight ? Variant.Filled : Variant.Outlined)"
|
|
Size="Size.Small">Rot R</MudChip>
|
|
</MudStack>
|
|
</MudPaper>
|
|
</MudItem>
|
|
|
|
<!-- MODE PANEL -->
|
|
<MudItem xs="12">
|
|
<MudPaper Elevation="1" Class="pa-2">
|
|
<MudText Typo="Typo.subtitle2" Color="Color.Secondary">Mode & Flags</MudText>
|
|
|
|
<MudStack Row="true" Spacing="2" Class="mt-2">
|
|
<MudChip T="string" Color="Color.Info" Variant="Variant.Filled">
|
|
Mode: @RfHandleData.Mode
|
|
</MudChip>
|
|
|
|
<MudChip T="string" Color="@(RfHandleData.ModeSelect? Color.Warning: Color.Default)"
|
|
Variant="@(RfHandleData.ModeSelect ? Variant.Filled : Variant.Outlined)"
|
|
Size="Size.Small">ModeSelect</MudChip>
|
|
<MudChip T="string" Color="@(RfHandleData.Enable? Color.Success: Color.Default)"
|
|
Variant="@(RfHandleData.Enable ? Variant.Filled : Variant.Outlined)"
|
|
Size="Size.Small">Enable</MudChip>
|
|
</MudStack>
|
|
</MudPaper>
|
|
</MudItem>
|
|
|
|
</MudGrid>
|
|
</MudItem>
|
|
|
|
</MudGrid>
|
|
|
|
</MudCardContent>
|
|
</MudCard>
|
|
|
|
<MudOverlay Visible="@IsLoading" Absolute="true">
|
|
<MudProgressCircular Indeterminate="true" />
|
|
</MudOverlay>
|
|
|
|
@code {
|
|
// ===== Joystick visual helpers =====
|
|
|
|
private const double JoyRadius = 70f; // px
|
|
private const double JoyCenter = 110f;
|
|
|
|
private double SafeLinear =>
|
|
(!RfHandleData.RemoteReady || RfHandleData.EStop)
|
|
? 0
|
|
: Math.Clamp(RfHandleData.Linear, -1, 1);
|
|
|
|
private double SafeAngular =>
|
|
(!RfHandleData.RemoteReady || RfHandleData.EStop)
|
|
? 0
|
|
: Math.Clamp(RfHandleData.Angular, -1, 1);
|
|
|
|
private double JoyX =>
|
|
JoyCenter + SafeAngular * JoyRadius;
|
|
|
|
private double JoyY =>
|
|
JoyCenter - SafeLinear * JoyRadius;
|
|
|
|
private string JoyColor =>
|
|
(!RfHandleData.RemoteReady || RfHandleData.EStop)
|
|
? "#666"
|
|
: "#bdbdbd";
|
|
|
|
private RfHandleDataDto RfHandleData = new();
|
|
private bool IsReloading = false;
|
|
private bool AutoReloadEnabled = false;
|
|
private string DeviceName = "";
|
|
private System.Threading.PeriodicTimer? _autoReloadTimer;
|
|
private readonly System.Threading.CancellationTokenSource _cancellationTokenSource = new();
|
|
|
|
[Parameter] public string DeviceId { get; set; } = "";
|
|
|
|
private bool IsLoading => !RfHandleHubClient.IsConnected;
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
if (!firstRender)
|
|
return;
|
|
|
|
try
|
|
{
|
|
await RfHandleHubClient.StartAsync();
|
|
|
|
var info = await RfHandleHubClient.GetDeviceInfoAsync(DeviceId);
|
|
DeviceName = info?.DeviceName ?? DeviceId;
|
|
|
|
RfHandleData = await RfHandleHubClient.GetRfHandleDataAsync(DeviceId);
|
|
|
|
StateHasChanged();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"Connect failed: {ex.Message}", Severity.Error);
|
|
}
|
|
}
|
|
|
|
private async Task ReloadRfHandleDataAsync()
|
|
{
|
|
if (!RfHandleHubClient.IsConnected || IsReloading)
|
|
return;
|
|
|
|
try
|
|
{
|
|
IsReloading = true;
|
|
StateHasChanged();
|
|
|
|
RfHandleData = await RfHandleHubClient.GetRfHandleDataAsync(DeviceId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"Failed to reload RfHandle data: {ex.Message}", Severity.Error);
|
|
}
|
|
finally
|
|
{
|
|
IsReloading = false;
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
private async Task ToggleAutoReloadAsync()
|
|
{
|
|
if (AutoReloadEnabled)
|
|
{
|
|
await StopAutoReloadAsync();
|
|
}
|
|
else
|
|
{
|
|
await StartAutoReloadAsync();
|
|
}
|
|
}
|
|
|
|
private async Task StartAutoReloadAsync()
|
|
{
|
|
if (AutoReloadEnabled || !RfHandleHubClient.IsConnected)
|
|
return;
|
|
|
|
AutoReloadEnabled = true;
|
|
_autoReloadTimer = new System.Threading.PeriodicTimer(TimeSpan.FromSeconds(0.05));
|
|
|
|
_ = Task.Run(async () =>
|
|
{
|
|
try
|
|
{
|
|
while (await _autoReloadTimer.WaitForNextTickAsync(_cancellationTokenSource.Token))
|
|
{
|
|
if (!_cancellationTokenSource.Token.IsCancellationRequested && RfHandleHubClient.IsConnected)
|
|
{
|
|
await InvokeAsync(async () =>
|
|
{
|
|
await ReloadRfHandleDataAsync();
|
|
});
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
catch (System.OperationCanceledException)
|
|
{
|
|
// Expected when cancelling
|
|
}
|
|
});
|
|
|
|
StateHasChanged();
|
|
}
|
|
|
|
private async Task StopAutoReloadAsync()
|
|
{
|
|
if (!AutoReloadEnabled)
|
|
return;
|
|
|
|
AutoReloadEnabled = false;
|
|
if (_autoReloadTimer != null)
|
|
{
|
|
_autoReloadTimer.Dispose();
|
|
_autoReloadTimer = null;
|
|
}
|
|
|
|
StateHasChanged();
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
await StopAutoReloadAsync();
|
|
_cancellationTokenSource.Cancel();
|
|
_cancellationTokenSource.Dispose();
|
|
|
|
try
|
|
{
|
|
await RfHandleHubClient.StopAsync();
|
|
}
|
|
catch { }
|
|
}
|
|
private int AxisToPercent(double v)
|
|
{
|
|
// v ∈ [-1, +1] → [0, 100]
|
|
return (int)Math.Clamp((v + 1) * 50f, 0, 100f);
|
|
}
|
|
|
|
private string AxisText(double v)
|
|
{
|
|
return v.ToString("F2");
|
|
}
|
|
|
|
}
|