@page "/plc/controller" @rendermode InteractiveWebAssemblyNoPrerender @implements IAsyncDisposable @using RobotNet10.RobotApp.Client.Clients @using RobotNet10.RobotApp.Client.Shared.Plc @using Microsoft.AspNetCore.SignalR.Client @using Microsoft.Extensions.DependencyInjection @using MudBlazor PLC Controller
@* Sticky Header Section *@
PLC Controller Status @* Connection Status *@ @if (hubClient?.IsConnected == true) { Connected to PlcControllerHub } else { Disconnected from PlcControllerHub } @* Control Section *@ Enable Update Disable Update @(isUpdateEnabled ? "Updating (2Hz)" : "Stopped")
@* Scrollable Content Area *@
@if (status != null) { @* System Status Card *@ System Status @(status.IsReady ? "Ready" : "Not Ready") Peripheral Mode: @status.PeripheralMode Safety Speed: @status.SafetySpeed Stop State: @status.StopState @* Lift State Card *@ Lift State @StatusIndicator(("Lifted Up", status.LiftedUp, false)) @StatusIndicator(("Lifted Down", status.LiftedDown, false)) @StatusIndicator(("Lift Home", status.LiftHome, false)) @* (Lift Module control removed; now use LiftModuleCard in Motion/ManualControl page) *@ @* Motor State Card *@ Motor State @StatusIndicator(("Left Motor", status.LeftMotorReady, false)) @StatusIndicator(("Right Motor", status.RightMotorReady, false)) @StatusIndicator(("Lift Motor", status.LiftMotorReady, false)) @* Safety Sensors Card *@ Safety Sensors @StatusIndicator(("Emergency", status.Emergency, true)) @StatusIndicator(("Bumper", status.Bumper, true)) @StatusIndicator(("Lidar Front", status.LidarFrontProtectField, true)) @StatusIndicator(("Lidar Back", status.LidarBackProtectField, true)) @StatusIndicator(("Lidar Tim", status.LidarFrontTimProtectField, true)) @* Button State Card *@ Button State @StatusIndicator(("Start Button", status.ButtonStart, false)) @StatusIndicator(("Stop Button", status.ButtonStop, true)) @StatusIndicator(("Reset Button", status.ButtonReset, false)) @* Other State Card *@ Other State @StatusIndicator(("Has Load", status.HasLoad, false)) @StatusIndicator(("Charger Enabled", status.EnabledCharger, false)) @StatusIndicator(("Charging", status.Charging, false)) @StatusIndicator(("Muted Base", status.MutedBase, false)) @StatusIndicator(("Muted Load", status.MutedLoad, false)) @* Command State Card - Các lệnh đã ghi xuống PLC *@ Command State System State: @status.CurrentSystemState Operation State: @status.CurrentOperationState RF Mode: @status.CurrentRFMode @* Command Values Card - Các giá trị điều khiển đã ghi *@ Command Values @StatusIndicator(("Horizontal Load", status.SetHorizontalLoadValue, false)) @StatusIndicator(("Muted Base (Set)", status.SetMutedBaseValue, false)) @StatusIndicator(("Muted Load (Set)", status.SetMutedLoadValue, false)) @* Command Values Card - Các giá trị điều khiển đã ghi *@ Command Values @StatusIndicator(("Charger Enable (Set)", status.SetEnableChargerValue, false)) @StatusIndicator(("Has Load (Set)", status.SetHasLoadValue, false)) @StatusIndicator(("RF E-Stop (Set)", status.SetRFEStopValue, true)) @StatusIndicator(("Batter Low (Set)", status.SetBatteryLowValue, true)) }
@code { [Inject] private IServiceProvider ServiceProvider { get; set; } = null!; [Inject] private ISnackbar Snackbar { get; set; } = null!; [Inject] private HttpClient Http { get; set; } = null!; private PlcControllerHubClient? hubClient; private PlcControllerStatusDto? status; private bool isLoading = false; private bool isHubReady = false; private bool isUpdateEnabled = false; private System.Threading.Timer? updateTimer; private bool _disposed = false; protected override async Task OnInitializedAsync() { hubClient = ServiceProvider.GetService(); if (hubClient is null) { isHubReady = false; return; // PLC hub client not registered (disconnected) } // Subscribe to connection state changes hubClient.ConnectionStateChanged += OnConnectionStateChanged; try { await hubClient.StartAsync(); isHubReady = hubClient.IsConnected; // Wait a bit for connection to establish await Task.Delay(500); isHubReady = hubClient.IsConnected; } catch (Exception ex) { Snackbar.Add($"Error initializing: {ex.Message}", Severity.Error); isHubReady = false; } } private void OnConnectionStateChanged(HubConnectionState state) { isHubReady = state == HubConnectionState.Connected; // Stop update if disconnected if (!isHubReady && isUpdateEnabled) { DisableUpdate(); } InvokeAsync(StateHasChanged); } private void EnableUpdate() { if (_disposed || isUpdateEnabled || !isHubReady) return; isUpdateEnabled = true; // Start timer at 2Hz (500ms) updateTimer = new System.Threading.Timer( async _ => await RefreshStatus(), null, TimeSpan.Zero, TimeSpan.FromMilliseconds(500)); StateHasChanged(); } private void DisableUpdate() { if (!isUpdateEnabled) return; isUpdateEnabled = false; // Stop and dispose timer updateTimer?.Change(Timeout.Infinite, Timeout.Infinite); updateTimer?.Dispose(); updateTimer = null; StateHasChanged(); } private async Task RefreshStatus() { if (_disposed || isLoading || hubClient is null || !isHubReady) return; try { isLoading = true; status = await hubClient.GetStatusAsync(); await InvokeAsync(StateHasChanged); } catch (Exception ex) { Snackbar.Add($"Error refreshing status: {ex.Message}", Severity.Error); isHubReady = false; DisableUpdate(); } finally { isLoading = false; } } private Color GetModeColor(string mode) { return mode switch { "AUTOMATIC" => Color.Success, "MANUAL" => Color.Warning, "SERVICE" => Color.Info, _ => Color.Default }; } private Color GetSpeedColor(string speed) { return speed switch { "Very_Slow" => Color.Error, "Slow" => Color.Warning, "Normal" => Color.Default, "Medium" => Color.Info, "Optimal" => Color.Success, "Fast" => Color.Primary, "Very_Fast" => Color.Secondary, _ => Color.Default }; } private Color GetStopStateColor(string state) { return state switch { "None" => Color.Success, "EMC" => Color.Error, "Bumper" => Color.Error, "FrontProtective" => Color.Warning, "BackProtective" => Color.Warning, "TimProtective" => Color.Warning, _ => Color.Default }; } private Color GetSystemStateColor(string state) { return state switch { "INIT" => Color.Info, "IDLE" => Color.Default, "PAUSED" => Color.Warning, "PROCCESSING" => Color.Primary, "DOCKING" => Color.Secondary, "CHARGING" => Color.Tertiary, "MAINTENANCE" => Color.Warning, "MANUAL" => Color.Info, "OVERRIDE" => Color.Warning, "ERROR" => Color.Error, _ => Color.Default }; } private Color GetOperationStateColor(string state) { return state switch { "Move" => Color.Primary, "Lifting" => Color.Info, "LiftRotating" => Color.Secondary, "None" => Color.Default, _ => Color.Default }; } private Color GetRFModeColor(string mode) { return mode switch { "Default" => Color.Success, "Maintenance" => Color.Warning, "Override" => Color.Error, "None" => Color.Default, _ => Color.Default }; } public async ValueTask DisposeAsync() { if (_disposed) return; _disposed = true; // Stop update timer DisableUpdate(); // Unsubscribe from events if (hubClient != null) { hubClient.ConnectionStateChanged -= OnConnectionStateChanged; } } } @* Status Indicator Component *@ @code { private RenderFragment<(string Label, bool Value, bool DangerWhenTrue)> StatusIndicator => context => @ @context.Label: @(context.Value ? "ON" : "OFF") ; }