Initial commit
This commit is contained in:
@@ -0,0 +1,359 @@
|
||||
@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");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user