374 lines
17 KiB
Plaintext
374 lines
17 KiB
Plaintext
@page "/robot-state"
|
|
@using RobotApp.Client.Services
|
|
@using RobotApp.VDA5050.State
|
|
@inject RobotStateClient RobotStateClient
|
|
@implements IDisposable
|
|
@rendermode InteractiveWebAssembly
|
|
|
|
<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>
|
|
@if (CurrentState != null)
|
|
{
|
|
<MudText Typo="Typo.subtitle2" Color="Color.Secondary">
|
|
@CurrentState.Version •
|
|
@CurrentState.Manufacturer •
|
|
@CurrentState.SerialNumber
|
|
</MudText>
|
|
}
|
|
else
|
|
{
|
|
<MudText Typo="Typo.subtitle2" Color="Color.Secondary">
|
|
Connecting to robot...
|
|
</MudText>
|
|
}
|
|
</div>
|
|
|
|
@if (CurrentState != null)
|
|
{
|
|
<MudChip T="string" Size="Size.Large"
|
|
Color="@(IsConnected ? Color.Success : Color.Error)"
|
|
Variant="Variant.Filled">
|
|
@(IsConnected ? "ONLINE" : "OFFLINE")
|
|
</MudChip>
|
|
}
|
|
</MudPaper>
|
|
|
|
@if (CurrentState == null)
|
|
{
|
|
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined" Class="mb-4">
|
|
<MudAlertTitle>Waiting for robot state data...</MudAlertTitle>
|
|
Connecting to SignalR hub and subscribing to robot updates.
|
|
<MudProgressLinear Indeterminate Class="mt-3" />
|
|
</MudAlert>
|
|
}
|
|
else
|
|
{
|
|
var msg = CurrentState;
|
|
|
|
<!-- ===================================================== -->
|
|
<!-- MESSAGE META -->
|
|
<!-- ===================================================== -->
|
|
<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">
|
|
Deviation: <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 (SOH)</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 since last node: @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" />
|
|
<MudTable Items="@MessageRows"
|
|
Dense="true"
|
|
Hover="true"
|
|
Bordered="true"
|
|
Elevation="0"
|
|
Height="200px"
|
|
FixedHeader="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>
|
|
<MudText Class="@(context.IsError ? "text-error" : "text-info")">
|
|
<b>@context.Type</b>
|
|
</MudText>
|
|
</MudTd>
|
|
<MudTd>
|
|
<MudChip T="string" Size="Size.Small"
|
|
Color="@(context.Level.Contains("ERROR", StringComparison.OrdinalIgnoreCase) ? Color.Error :
|
|
context.Level.Contains("WARN", StringComparison.OrdinalIgnoreCase) ? Color.Warning :
|
|
Color.Info)">
|
|
@context.Level
|
|
</MudChip>
|
|
</MudTd>
|
|
<MudTd>
|
|
<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="180px"
|
|
FixedHeader="true">
|
|
<HeaderContent>
|
|
<MudTh>Action</MudTh>
|
|
<MudTh>Action ID</MudTh>
|
|
<MudTh class="text-right">Status</MudTh>
|
|
</HeaderContent>
|
|
<RowTemplate>
|
|
<MudTd><MudText Typo="Typo.body2" Class="text-truncate" Title="@context.ActionType">@context.ActionType</MudText></MudTd>
|
|
<MudTd><MudText Typo="Typo.caption">@context.ActionId</MudText></MudTd>
|
|
<MudTd Class="text-right">
|
|
<MudChip T="string" Size="Size.Small"
|
|
Color="@(context.ActionStatus == "RUNNING" ? Color.Info :
|
|
context.ActionStatus == "FINISHED" ? Color.Success :
|
|
context.ActionStatus == "FAILED" ? Color.Error : Color.Default)">
|
|
@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 StateMsg? CurrentState;
|
|
private bool IsConnected => RobotStateClient.LatestStates.ContainsKey(RobotSerial);
|
|
|
|
// Thay bằng serial number thật của robot bạn muốn theo dõi
|
|
private readonly string RobotSerial = "T800-002";
|
|
|
|
private List<MessageRow> MessageRows = new();
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
// Subscribe sự kiện nhận state
|
|
RobotStateClient.OnStateReceived += OnRobotStateReceived;
|
|
|
|
// Bắt đầu kết nối SignalR (nếu chưa)
|
|
if (RobotStateClient.LatestStates.Count == 0)
|
|
{
|
|
await RobotStateClient.StartAsync();
|
|
}
|
|
|
|
// Đăng ký theo dõi robot cụ thể
|
|
await RobotStateClient.SubscribeRobotAsync(RobotSerial);
|
|
|
|
// Lấy state hiện tại nếu đã có
|
|
CurrentState = RobotStateClient.GetLatestState(RobotSerial);
|
|
UpdateMessageRows();
|
|
}
|
|
|
|
private void OnRobotStateReceived(string serialNumber, StateMsg state)
|
|
{
|
|
if (serialNumber != RobotSerial) return;
|
|
|
|
InvokeAsync(() =>
|
|
{
|
|
CurrentState = state;
|
|
UpdateMessageRows();
|
|
StateHasChanged();
|
|
});
|
|
}
|
|
|
|
private void UpdateMessageRows()
|
|
{
|
|
MessageRows.Clear();
|
|
|
|
if (CurrentState?.Errors != null)
|
|
{
|
|
foreach (var err in CurrentState.Errors)
|
|
{
|
|
MessageRows.Add(new MessageRow(err.ErrorType ?? "-", err.ErrorLevel ?? "ERROR", err.ErrorDescription ?? "", true));
|
|
}
|
|
}
|
|
|
|
if (CurrentState?.Information != null)
|
|
{
|
|
foreach (var info in CurrentState.Information)
|
|
{
|
|
MessageRows.Add(new MessageRow(info.InfoType ?? "-", info.InfoLevel ?? "INFO", info.InfoDescription ?? "", false));
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
RobotStateClient.OnStateReceived -= OnRobotStateReceived;
|
|
}
|
|
|
|
private record MessageRow(string Type, string Level, string Description, bool IsError);
|
|
} |