@using MudBlazor @using Microsoft.AspNetCore.SignalR.Client @using RobotNet10.RobotApp.Client.Clients @using RobotNet10.Shared.Sensor @implements IAsyncDisposable @inject BatteryHubClient BatteryHubClient @inject ISnackbar Snackbar @DeviceName @DeviceId @* =================== SVG KHÔNG ĐỔI =================== *@ @* ==== Charge Level Fill ==== *@ @{ var clipHeight = 196.0; var clipY = 22.0; var clipBottom = clipY + clipHeight; var healthHeight = BatteryData.Percentage / 100.0 * clipHeight; var healthY = clipBottom - healthHeight; } @((int)BatteryData.Percentage)% Charge Level @GetHealthPercentage()% Health (SOH) @BatteryData.Voltage.ToString("F2") V Voltage @BatteryData.Current.ToString("F2") A Current @(IsCharging() ? "Charging" : "Discharging") @if (BatteryData.CellTemperature != null && BatteryData.CellTemperature.Length > 0) { @BatteryData.CellTemperature[0].ToString("F1") °C Temperature } @if (!double.IsNaN(BatteryData.Charge)) { @BatteryData.Charge.ToString("F2") Ah Remaining Capacity } @if (!double.IsNaN(BatteryData.Capacity)) { @BatteryData.Capacity.ToString("F2") Ah Full Capacity } Last Update: @BatteryData.Header.Stamp.ToString("yyyy-MM-dd HH:mm:ss") @code { [Parameter, EditorRequired] public string DeviceId { get; set; } = string.Empty; private string DeviceName { get; set; } = string.Empty; private BatteryState BatteryData = new(); private bool IsLoading => !BatteryHubClient.IsConnected; private bool IsReloading = false; protected override async Task OnInitializedAsync() => await base.OnInitializedAsync(); protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender && !string.IsNullOrEmpty(DeviceId)) await ConnectAsync(); await base.OnAfterRenderAsync(firstRender); } private async Task ConnectAsync() { try { await BatteryHubClient.StartAsync(); var deviceInfo = await BatteryHubClient.GetDeviceInfoAsync(DeviceId); DeviceName = deviceInfo?.DeviceName ?? DeviceId; BatteryData = await BatteryHubClient.GetBatteryDataAsync(DeviceId); StateHasChanged(); } catch (Exception ex) { Snackbar.Add($"Failed to connect: {ex.Message}", Severity.Error); } } private async Task DisconnectAsync() { try { await BatteryHubClient.StopAsync(); BatteryData = new BatteryState(); StateHasChanged(); } catch (Exception ex) { Snackbar.Add($"Failed to disconnect: {ex.Message}", Severity.Error); } } private async Task ReloadBatteryDataAsync() { if (!BatteryHubClient.IsConnected || IsReloading) return; try { IsReloading = true; StateHasChanged(); BatteryData = await BatteryHubClient.GetBatteryDataAsync(DeviceId); } catch (Exception ex) { Snackbar.Add($"Failed to reload battery data: {ex.Message}", Severity.Error); } finally { IsReloading = false; StateHasChanged(); } } private Color GetChargeLevelColor() => GetHealthPercentage() switch { >= 70 => Color.Success, >= 30 => Color.Warning, _ => Color.Error }; private Color GetCurrentColor() => IsCharging() ? Color.Success : Color.Info; private string GetHealthColor() => BatteryData.Percentage switch { >= 70 => "#4caf50", >= 30 => "#ff9800", _ => "#f44336" }; private bool IsCharging() => BatteryData.PowerSupplyStatus == BatteryState.PowerSupplyStatusCharging; private double GetHealthPercentage() { return BatteryData.PowerSupplyHealth switch { BatteryState.PowerSupplyHealthGood => 100.0, BatteryState.PowerSupplyHealthOverheat => 50.0, BatteryState.PowerSupplyHealthDead => 0.0, BatteryState.PowerSupplyHealthOvervoltage => 30.0, BatteryState.PowerSupplyHealthUnspecifiedFailure => 20.0, BatteryState.PowerSupplyHealthCold => 40.0, _ => 0.0 }; } private string GetHealthTextColor() => "var(--mud-palette-text-primary)"; public async ValueTask DisposeAsync() => await DisconnectAsync(); }