@page "/robot-state" @using RobotApp.Client.Services @using RobotApp.VDA5050.State @inject RobotStateClient RobotStateClient @implements IDisposable @rendermode InteractiveWebAssembly
🤖 VDA 5050 Robot Dashboard @if (CurrentState != null) { @CurrentState.Version • @CurrentState.Manufacturer • @CurrentState.SerialNumber } else { Connecting to robot... }
@if (CurrentState != null) { @(IsConnected ? "ONLINE" : "OFFLINE") }
@if (CurrentState == null) { Waiting for robot state data... Connecting to SignalR hub and subscribing to robot updates. } else { var msg = CurrentState; HeaderId @msg.HeaderId Timestamp (UTC) @msg.Timestamp Version @msg.Version OrderUpdateId @msg.OrderUpdateId 📍 Position & Velocity NewBase: @(msg.NewBaseRequest ? "TRUE" : "FALSE") X: @msg.AgvPosition.X.ToString("F2") m Y: @msg.AgvPosition.Y.ToString("F2") m θ: @msg.AgvPosition.Theta.ToString("F2") rad Vx: @msg.Velocity.Vx.ToString("F2") m/s Vy: @msg.Velocity.Vy.ToString("F2") m/s Ω: @msg.Velocity.Omega.ToString("F3") rad/s Initialized: @(msg.AgvPosition.PositionInitialized ? "TRUE" : "FALSE") Deviation: @msg.AgvPosition.DeviationRange Localization Score: @(msg.AgvPosition.LocalizationScore * 100) % 🔋 Battery @msg.BatteryState.BatteryCharge:F1 % @(msg.BatteryState.Charging ? "⚡ Charging" : "Discharging") Voltage @msg.BatteryState.BatteryVoltage.ToString("F1") V Health (SOH) @msg.BatteryState.BatteryHealth % Reach @((int)msg.BatteryState.Reach) m 🧭 Order & Path Order ID: @(msg.OrderId ?? "—") Update ID: @msg.OrderUpdateId Last Node: @msg.LastNodeId Seq: @msg.LastNodeSequenceId Distance since last node: @msg.DistanceSinceLastNode:F1 m @{ 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; }
Nodes: @nodeReleased / @nodeTotal Edges: @edgeReleased / @edgeTotal @(msg.Driving ? "DRIVING" : "STOPPED") @(msg.Paused ? "PAUSED" : "ACTIVE")
🚨 Errors & Information Type Level Description @context.Type @context.Level @context.Description No errors or information messages ⚙️ Actions Action Action ID Status @context.ActionType @context.ActionId @context.ActionStatus No active actions 🛑 Safety E-STOP: @msg.SafetyState.EStop Field Violation: @(msg.SafetyState.FieldViolation ? "YES" : "NO")
}
@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 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); }