261 lines
11 KiB
Plaintext
261 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 rounded">
|
|
<div class="progress p-0" style="height:2px;">
|
|
<span class="progress-bar" role="progressbar" style="width: @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">@status.OutputVoltage</div>
|
|
V
|
|
</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">@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>
|
|
<span>Solar Power</span>
|
|
</h5>
|
|
<div class="card-body pt-0">
|
|
<div class="container text-center fw-bold p-0">
|
|
<div class="row 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">@status.PVInputVoltage</div>
|
|
V
|
|
</div>
|
|
<div class="col">
|
|
<div class="fs-5 text-muted">@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"></span>
|
|
<span>Battery</span>
|
|
</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">@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-6 voltage">@GetChargeMode()</div>
|
|
<div class="charge-discharge">@status.BatteryVoltage</div>
|
|
<div class="fs-4">V</div>
|
|
<div class="fs-6 crate">@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;
|
|
}
|
|
|
|
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 status.BatteryChargeCRate;
|
|
|
|
if (status?.BatteryDischargeCRate > 0)
|
|
return status.BatteryDischargeCRate;
|
|
|
|
return 0;
|
|
}
|
|
|
|
private static string GetChargeMode()
|
|
=> status?.ChargeMode is ChargeMode.NONE
|
|
? "VOLTAGE"
|
|
: status?.ChargeMode.ToString() ?? string.Empty;
|
|
|
|
} |