266 lines
11 KiB
Plaintext
266 lines
11 KiB
Plaintext
@page "/"
|
|
@using InverterMon.Shared.Models
|
|
@using System.Text.Json
|
|
@implements IDisposable
|
|
|
|
<PageTitle>Dashboard</PageTitle>
|
|
|
|
<Loader Enabled=@(status is null)/>
|
|
|
|
@if (status is not null)
|
|
{
|
|
<div class="container">
|
|
|
|
@if (status?.GridUsageWatts > 100)
|
|
{
|
|
<div class="row">
|
|
<div class="col-sm-12">
|
|
<div class="card">
|
|
<h5 class="card-header">
|
|
<span class="oi oi-power-standby" aria-hidden="true"></span>
|
|
Grid Usage
|
|
</h5>
|
|
<div class="card-body pt-0">
|
|
<div class="container text-center fw-bold p-0">
|
|
<div class="row bg-light rounded">
|
|
<div class="col">
|
|
<div class="fs-1 text-danger">@status?.GridUsageWatts</div>
|
|
W
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
<div class="row mt-2">
|
|
<div class="col-sm-6">
|
|
<div class="card">
|
|
<h5 class="card-header">
|
|
<span class="oi oi-lightbulb"></span>
|
|
<span>Output Load</span>
|
|
</h5>
|
|
<div class="card-body pt-0">
|
|
<div class="container text-center fw-bold p-0">
|
|
<div class="row bg-light rounded">
|
|
<div class="progress p-0" style="height:2px;">
|
|
<span class="progress-bar" role="progressbar" style="width: @RoundToWholeNumber(status?.LoadPercentage)%" aria-valuenow="25"
|
|
aria-valuemin="0" aria-valuemax="100"></span>
|
|
</div>
|
|
<div class="col">
|
|
<div class="fs-1 text-danger">@status?.LoadWatts</div>
|
|
W
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col">
|
|
<div class="fs-5 text-muted">@RoundToWholeNumber(status?.OutputVoltage)</div>
|
|
<span class="text-body">
|
|
V
|
|
</span>
|
|
</div>
|
|
<div class="col">
|
|
<div class="row h-100 align-content-center">
|
|
<span class="fs-6 text-muted">
|
|
@(status?.WorkingMode)
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="col">
|
|
<div class="fs-5 text-muted">@RoundToOneDecimal(status?.LoadCurrent)</div>
|
|
A
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-sm-6">
|
|
<div class="card mt-2 mt-sm-0">
|
|
<h5 class="card-header">
|
|
<span class="oi oi-sun" aria-hidden="true"/>
|
|
<span>Solar Power</span>
|
|
</h5>
|
|
<div class="card-body pt-0">
|
|
<div class="container text-center fw-bold p-0">
|
|
<div class="row bg-light rounded">
|
|
<div class="progress p-0" style="height:2px;">
|
|
<span class="progress-bar" role="progressbar" style="width: @status?.PVPotential%" aria-valuenow="25" aria-valuemin="0"
|
|
aria-valuemax="100"></span>
|
|
</div>
|
|
<div class="col">
|
|
<div class="fs-1 text-success">@status?.PVInputWatt</div>
|
|
W
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col">
|
|
<div class="fs-5 text-muted">@RoundToWholeNumber(status?.PVInputVoltage)</div>
|
|
<span class="text-body">
|
|
V
|
|
</span>
|
|
</div>
|
|
<div class="col">
|
|
<div class="fs-5 text-muted">@RoundToOneDecimal(status?.PVInputCurrent)</div>
|
|
A
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="container pt-2">
|
|
<div class="row">
|
|
<div class="col-sm-12">
|
|
<div class="card">
|
|
<h5 class="card-header align-self-center w-100">
|
|
<span class="oi oi-battery-empty" aria-hidden="true"/>
|
|
Battery
|
|
</h5>
|
|
<div class="card-body p-0 m-0">
|
|
<div class="container text-center m-0 p-0">
|
|
<div class="row m-0 p-0">
|
|
<div class="progress p-0" style="height:2px;">
|
|
<span class="progress-bar" role="progressbar" style="width: @status?.BatteryDischargePotential%" aria-valuenow="25"
|
|
aria-valuemin="0" aria-valuemax="100"></span>
|
|
</div>
|
|
</div>
|
|
<div class="row mt-2">
|
|
<div class="col-4">
|
|
<span class="fs-6 fw-bold text-muted charge-discharge">Charging</span>
|
|
<div class="fw-bold">
|
|
<div class="fs-1 text-danger">@status?.BatteryChargeWatts</div>
|
|
W
|
|
<div class="fw-bold">
|
|
<div class="fs-5 text-muted">@RoundToOneDecimal(status?.BatteryChargeCurrent)</div>
|
|
A
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-4">
|
|
<div class="container bg-light rounded-1 p-1 m-0 fw-bold text-muted fs-1">
|
|
<div class="fs-4 voltage">Voltage</div>
|
|
<div class="text-black charge-discharge">@RoundToOneDecimal(status?.BatteryVoltage)</div>
|
|
<div class="fs-4 text-black-50">V</div>
|
|
<div class="fs-6 bg-white text-dark">@GetCRate() C</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-4">
|
|
<span class="fs-6 fw-bold text-muted charge-discharge">Discharging</span>
|
|
<div class="fw-bold">
|
|
<div class="fs-1 text-success">@status?.BatteryDischargeWatts</div>
|
|
W
|
|
<div class="fw-bold">
|
|
<div class="fs-5 text-muted">@status?.BatteryDischargeCurrent</div>
|
|
A
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
@code{
|
|
private static event Action<InverterStatus?>? onStatusUpdated;
|
|
private static event Action? onStatusRetrievalError;
|
|
private static InverterStatus? status;
|
|
|
|
protected override void OnInitialized()
|
|
{
|
|
onStatusUpdated += UpdateState;
|
|
onStatusRetrievalError += NullifyStatus;
|
|
}
|
|
|
|
private void NullifyStatus()
|
|
{
|
|
status = null;
|
|
StateHasChanged();
|
|
}
|
|
|
|
private void UpdateState(InverterStatus? s)
|
|
{
|
|
status = s;
|
|
StateHasChanged();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
onStatusUpdated -= UpdateState;
|
|
onStatusRetrievalError -= NullifyStatus;
|
|
}
|
|
|
|
private static double RoundToWholeNumber(double? val)
|
|
=> Math.Round(val ?? 0, 0);
|
|
|
|
private static double RoundToOneDecimal(double? val)
|
|
=> Math.Round(val ?? 0, 1);
|
|
|
|
public static async Task StartStatusStreaming(string basePath)
|
|
{
|
|
//note: only reason we have a full-time stream download is because there's a bug in
|
|
// blazor-wasm that doesn't close the fetch http requests when streaming is involved.
|
|
// and it leads to a new stream download being created everytime a page is initialized.
|
|
// which leads to a memory leak/ connection exhaustion.
|
|
|
|
using var client = new HttpClient();
|
|
client.BaseAddress = new(basePath);
|
|
client.Timeout = TimeSpan.FromSeconds(5);
|
|
|
|
var retryDelay = 1000;
|
|
|
|
while (true)
|
|
{
|
|
try
|
|
{
|
|
using var request = new HttpRequestMessage(HttpMethod.Get, "api/status");
|
|
request.SetBrowserResponseStreamingEnabled(true);
|
|
|
|
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
using var stream = await response.Content.ReadAsStreamAsync();
|
|
|
|
await foreach (var s in
|
|
JsonSerializer.DeserializeAsyncEnumerable<InverterStatus>(
|
|
stream,
|
|
new JsonSerializerOptions
|
|
{
|
|
PropertyNameCaseInsensitive = true,
|
|
DefaultBufferSize = 64
|
|
}))
|
|
{
|
|
onStatusUpdated?.Invoke(s);
|
|
retryDelay = 1000;
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
onStatusRetrievalError?.Invoke();
|
|
await Task.Delay(retryDelay);
|
|
retryDelay += 500;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static double GetCRate()
|
|
{
|
|
if (status?.BatteryChargeCRate > 0)
|
|
return Math.Round(status.BatteryChargeCRate, 2);
|
|
|
|
if (status?.BatteryDischargeCRate > 0)
|
|
return Math.Round(status.BatteryDischargeCRate, 2);
|
|
|
|
return 0;
|
|
}
|
|
|
|
} |