Initial commit

This commit is contained in:
2026-07-03 16:37:12 +07:00
commit 63b8c1ea8b
1931 changed files with 640587 additions and 0 deletions

View File

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