RobotApp/RobotApp.Client/Pages/State.razor
2025-12-20 17:57:54 +07:00

397 lines
18 KiB
Plaintext

@page "/robot-state"
@using RobotApp.VDA5050.State
@rendermode InteractiveWebAssemblyNoPrerender
@inject StateMsg RobotState
<MudContainer MaxWidth="MaxWidth.False" Class="pa-4">
<!-- ===================================================== -->
<!-- HEADER -->
<!-- ===================================================== -->
<MudPaper Class="pa-6 mb-4 d-flex align-center justify-space-between" Elevation="3">
<div>
<MudText Typo="Typo.h4">🤖 VDA 5050 Robot Dashboard</MudText>
<MudText Typo="Typo.subtitle2" Color="Color.Secondary">
@RobotState.Version •
@RobotState.Manufacturer
@RobotState.SerialNumber
</MudText>
</div>
@* @if (RobotState.Current != null)
{
<MudChip T="string" Size="Size.Large"
Color="@(RobotState.Current ? Color.Success : Color.Error)">
@(RobotState.Current.IsOnline ? "ONLINE" : "OFFLINE")
</MudChip>
} *@
</MudPaper>
@if (RobotState == null)
{
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined">
Waiting for robot state (VDA5050)...
<MudProgressLinear Indeterminate Class="mt-3" />
</MudAlert>
}
else
{
var msg = RobotState;
<!-- ===================================================== -->
<!-- 📨 MESSAGE META (DEBUG / TRACE) -->
<!-- ===================================================== -->
<MudPaper Class="pa-3 mb-4" Elevation="1">
<MudGrid Spacing="2">
<MudItem xs="12" md="3">
<MudText Typo="Typo.caption">HeaderId</MudText>
<MudText>@msg.HeaderId</MudText>
</MudItem>
<MudItem xs="12" md="5">
<MudText Typo="Typo.caption">Timestamp (UTC)</MudText>
<MudText>@msg.Timestamp</MudText>
</MudItem>
<MudItem xs="12" md="2">
<MudText Typo="Typo.caption">Version</MudText>
<MudText>@msg.Version</MudText>
</MudItem>
<MudItem xs="12" md="2">
<MudText Typo="Typo.caption">OrderUpdateId</MudText>
<MudChip T="string" Color="Color.Primary">
@msg.OrderUpdateId
</MudChip>
</MudItem>
</MudGrid>
</MudPaper>
<!-- ===================================================== -->
<!-- MAIN GRID -->
<!-- ===================================================== -->
<MudGrid Spacing="4">
<!-- POSITION + VELOCITY -->
<MudItem xs="12" md="6" lg="4">
<MudPaper Class="pa-5 h-100" Elevation="2">
<MudGrid AlignItems="AlignItems.Center">
<MudItem xs="8">
<MudText Typo="Typo.h6">📍 Position & Velocity</MudText>
</MudItem>
<MudItem xs="4" Class="d-flex justify-end">
<MudChip T="string" Size="Size.Small"
Color="@(msg.NewBaseRequest ? Color.Success : Color.Error)">
NewBase: @(msg.NewBaseRequest ? "TRUE" : "FALSE")
</MudChip>
</MudItem>
</MudGrid>
<MudDivider Class="my-3" />
<MudGrid Spacing="1">
<MudItem xs="6">
<MudText>X: <b>@msg.AgvPosition.X.ToString("F2")</b> m</MudText>
<MudText>Y: <b>@msg.AgvPosition.Y.ToString("F2")</b> m</MudText>
<MudText>θ: <b>@msg.AgvPosition.Theta.ToString("F2")</b> rad</MudText>
</MudItem>
<MudItem xs="6">
<MudText>Vx: <b>@msg.Velocity.Vx.ToString("F2")</b> m/s</MudText>
<MudText>Vy: <b>@msg.Velocity.Vy.ToString("F2")</b> m/s</MudText>
<MudText>Ω: <b>@msg.Velocity.Omega.ToString("F3")</b> rad/s</MudText>
</MudItem>
</MudGrid>
<MudDivider Class="my-3" />
<MudGrid Spacing="1">
<MudItem xs="6">
<MudChip T="string" Size="Size.Small"
Color="@(msg.AgvPosition.PositionInitialized ? Color.Success : Color.Error)">
Initialized: @(msg.AgvPosition.PositionInitialized ? "TRUE" : "FALSE")
</MudChip>
</MudItem>
<MudItem xs="6" Class="d-flex justify-end">
<MudText Typo="Typo.caption">
DeviationRange: <b>@msg.AgvPosition.DeviationRange</b>
</MudText>
</MudItem>
</MudGrid>
<MudProgressLinear Value="@(msg.AgvPosition.LocalizationScore * 100)"
Class="mt-3"
Color="Color.Success" />
<MudText Typo="Typo.caption">
Localization Score: @(msg.AgvPosition.LocalizationScore * 100) %
</MudText>
</MudPaper>
</MudItem>
<!-- BATTERY -->
<MudItem xs="12" md="6" lg="4">
<MudPaper Class="pa-5 h-100" Elevation="2">
<MudText Typo="Typo.h6">🔋 Battery</MudText>
<MudDivider Class="my-3" />
<MudProgressLinear Value="@msg.BatteryState.BatteryCharge"
Size="Size.Large"
Rounded
Color="@(msg.BatteryState.BatteryCharge > 50 ? Color.Success :
msg.BatteryState.BatteryCharge > 20 ? Color.Warning : Color.Error)" />
<MudText Typo="Typo.h4" Class="mt-2">
@msg.BatteryState.BatteryCharge:F1 %
</MudText>
<MudChip T="string" Size="Size.Small"
Color="@(msg.BatteryState.Charging ? Color.Info : Color.Default)">
@(msg.BatteryState.Charging ? "⚡ Charging" : "Discharging")
</MudChip>
<MudDivider Class="my-3" />
<MudGrid Spacing="1">
<MudItem xs="4">
<MudText Typo="Typo.caption">Voltage</MudText>
<MudText><b>@msg.BatteryState.BatteryVoltage.ToString("F1")</b> V</MudText>
</MudItem>
<MudItem xs="4">
<MudText Typo="Typo.caption">Health</MudText>
<MudText><b>@msg.BatteryState.BatteryHealth</b> %</MudText>
</MudItem>
<MudItem xs="4">
<MudText Typo="Typo.caption">Reach</MudText>
<MudText><b>@((int)msg.BatteryState.Reach)</b> m</MudText>
</MudItem>
</MudGrid>
</MudPaper>
</MudItem>
<!-- ORDER & PATH -->
<MudItem xs="12" md="6" lg="4">
<MudPaper Class="pa-5 h-100" Elevation="2">
<MudText Typo="Typo.h6">🧭 Order & Path</MudText>
<MudDivider Class="my-3" />
<MudText>Order ID: <b>@(msg.OrderId ?? "—")</b></MudText>
<MudText>Update ID: <b>@msg.OrderUpdateId</b></MudText>
<MudDivider Class="my-2" />
<MudText>
Last Node: <b>@msg.LastNodeId</b>
<MudText Typo="Typo.caption" Inline="true">Seq: @msg.LastNodeSequenceId</MudText>
</MudText>
<MudText>Distance: @msg.DistanceSinceLastNode:F1 m</MudText>
<MudDivider Class="my-3" />
@{
var nodeReleased = msg.NodeStates?.Count(n => n.Released) ?? 0;
var nodeTotal = msg.NodeStates?.Length ?? 0;
var edgeReleased = msg.EdgeStates?.Count(e => e.Released) ?? 0;
var edgeTotal = msg.EdgeStates?.Length ?? 0;
}
<div class="d-flex align-center flex-wrap gap-2">
<MudChip T="string" Color="Color.Info">Nodes: @nodeReleased / @nodeTotal</MudChip>
<MudChip T="string" Color="Color.Info">Edges: @edgeReleased / @edgeTotal</MudChip>
<MudChip T="string" Size="Size.Small" Color="@(msg.Driving ? Color.Success : Color.Default)">
@(msg.Driving ? "DRIVING" : "STOPPED")
</MudChip>
<MudChip T="string" Size="Size.Small" Color="@(msg.Paused ? Color.Warning : Color.Success)">
@(msg.Paused ? "PAUSED" : "ACTIVE")
</MudChip>
</div>
</MudPaper>
</MudItem>
<!-- ERRORS + INFORMATION -->
<MudItem xs="12" md="12" lg="6">
<MudPaper Class="pa-5 h-100" Elevation="2">
<MudText Typo="Typo.h6">🚨 Errors & Information</MudText>
<MudDivider Class="my-3" />
@{
var rows = new List<MessageRow>();
if (msg.Errors != null)
{
foreach (var err in msg.Errors)
{
rows.Add(new MessageRow(
err.ErrorType ?? "-",
err.ErrorLevel ?? "-",
err.ErrorDescription ?? "",
true
));
}
}
if (msg.Information != null)
{
foreach (var info in msg.Information)
{
rows.Add(new MessageRow(
info.InfoType ?? "-",
info.InfoLevel ?? "-",
info.InfoDescription ?? "",
false
));
}
}
var sortedMessages = rows
.OrderBy(r => r.IsError ? 0 : 1) // Errors trước
.ThenBy(r => r.Type)
.ToList();
}
<MudTable Items="@sortedMessages"
Dense="true"
Hover="true"
Bordered="true"
Elevation="0"
Height="180px"
Breakpoint="Breakpoint.Sm"
HorizontalScrollbar="true">
<ColGroup>
<col style="width: 35%" />
<col style="width: 20%" />
<col />
</ColGroup>
<HeaderContent>
<MudTh>Type</MudTh>
<MudTh>Level</MudTh>
<MudTh>Description</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Type">
<MudText Class="@(context.IsError ? "text-error" : "text-info")">
<b>@context.Type</b>
</MudText>
</MudTd>
<MudTd DataLabel="Level">
<MudChip T="string"
Size="Size.Small"
Color="@(context.Level == "ERROR"
? Color.Error
: context.Level == "WARNING"
? Color.Warning
: Color.Info)">
@context.Level
</MudChip>
</MudTd>
<MudTd DataLabel="Description">
<MudText Typo="Typo.caption"
Class="text-truncate"
Title="@context.Description">
@context.Description
</MudText>
</MudTd>
</RowTemplate>
<NoRecordsContent>
<MudText Typo="Typo.caption"
Color="Color.Secondary"
Class="pa-4 text-center">
No errors or information messages
</MudText>
</NoRecordsContent>
</MudTable>
</MudPaper>
</MudItem>
<!-- ACTIONS -->
<MudItem xs="12" md="6" lg="3">
<MudPaper Class="pa-5 h-100" Elevation="2">
<MudText Typo="Typo.h6">⚙️ Actions</MudText>
<MudDivider Class="my-3" />
<MudTable Items="msg.ActionStates"
Dense="true"
Hover="true"
Bordered="true"
Elevation="0"
Height="160px"
Breakpoint="Breakpoint.Sm"
HorizontalScrollbar="true"
FixedHeader="true">
<ColGroup>
<col style="width: 40%" />
<col style="width: 35%" />
<col style="width: 25%" />
</ColGroup>
<HeaderContent>
<MudTh>Action</MudTh>
<MudTh>Action ID</MudTh>
<MudTh class="text-right">Status</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Action">
<MudText Typo="Typo.body2"
Class="text-truncate"
Title="@context.ActionType">
@context.ActionType
</MudText>
</MudTd>
<MudTd DataLabel="Action ID">
<MudText Typo="Typo.caption">
@context.ActionId
</MudText>
</MudTd>
<MudTd DataLabel="Status" Class="text-right">
<MudChip T="string"
Size="Size.Small"
Variant="Variant.Filled"
Color="@(context.ActionStatus == "RUNNING"
? Color.Info
: context.ActionStatus == "FINISHED"
? Color.Success
: Color.Error)">
@context.ActionStatus
</MudChip>
</MudTd>
</RowTemplate>
<NoRecordsContent>
<MudText Typo="Typo.caption"
Color="Color.Secondary"
Class="pa-4 text-center">
No active actions
</MudText>
</NoRecordsContent>
</MudTable>
</MudPaper>
</MudItem>
<!-- SAFETY -->
<MudItem xs="12" md="6" lg="3">
<MudPaper Class="pa-5 h-100" Elevation="2">
<MudText Typo="Typo.h6">🛑 Safety</MudText>
<MudDivider Class="my-3" />
<MudChip T="string" Size="Size.Large" Class="w-100 mb-2"
Color="@(msg.SafetyState.EStop == "NONE" ? Color.Success : Color.Error)">
E-STOP: @msg.SafetyState.EStop
</MudChip>
<MudChip T="string" Size="Size.Large" Class="w-100"
Color="@(msg.SafetyState.FieldViolation ? Color.Error : Color.Success)">
Field Violation: @(msg.SafetyState.FieldViolation ? "YES" : "NO")
</MudChip>
</MudPaper>
</MudItem>
</MudGrid>
}
</MudContainer>
@code {
private async Task OnStateChanged()
=> await InvokeAsync(StateHasChanged);
private record MessageRow(
string Type,
string Level,
string Description,
bool IsError
);
}