wip: before inverter service

This commit is contained in:
djnitehawk 2025-03-12 14:28:38 +05:30 committed by Dĵ ΝιΓΞΗΛψΚ
parent 806ceefe4b
commit 7fca1d1cfb
30 changed files with 174 additions and 794 deletions

View File

@ -6,7 +6,7 @@
<Loader Enabled=@(settings is null)/> <Loader Enabled=@(settings is null)/>
@if(settings is not null) @if (settings is not null)
{ {
<ul class="nav nav-tabs" id="myTab" role="tablist"> <ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
@ -26,33 +26,12 @@
Max Combined Charge Current: Max Combined Charge Current:
</div> </div>
<div class="col-6 p-2 bg-secondary"> <div class="col-6 p-2 bg-secondary">
<input @bind-value=settings.MaxCombinedChargeCurrent class="form-control bg-light d-inline m-1" style="width:4rem;" type="text" maxlength="4">
@if (chargeAmpereValues == null || inProgressSetting == Setting.CombinedChargeCurrent) <button type="button" class="btn btn-light d-inline m-1"
{ @onclick="()=>SetSetting(Setting.CombinedChargeCurrent,settings.MaxCombinedChargeCurrent)">
<div class="spinner-border m-2"></div> <span class="@Spinner(Button.MaxCombinedChargeCurrent)"></span>
} <span class="@Hidden(Button.MaxCombinedChargeCurrent)">Save</span>
else </button>
{
<div class="dropdown p-1">
<button class="btn btn-secondary dropdown-toggle" type="button" id="combinedcurrent" data-bs-toggle="dropdown">
@Sanitize(settings.MaxCombinedChargeCurrent) A
</button>
@if (chargeAmpereValues != null)
{
<ul class="dropdown-menu dropdown-menu">
@foreach (var val in chargeAmpereValues!.CombinedAmpereValues)
{
<li>
<a class="dropdown-item @(settings.MaxCombinedChargeCurrent == val ? "active" : "")"
@onclick="()=>SetSetting(Setting.CombinedChargeCurrent,val)">
@Sanitize(val) A
</a>
</li>
}
</ul>
}
</div>
}
</div> </div>
</div> </div>
@ -61,33 +40,11 @@
Max Grid Charge Current: Max Grid Charge Current:
</div> </div>
<div class="col-6 p-2 bg-secondary"> <div class="col-6 p-2 bg-secondary">
<input @bind-value=settings.MaxACChargeCurrent class="form-control bg-light d-inline m-1" style="width:4rem;" type="text" maxlength="4">
@if (chargeAmpereValues == null || inProgressSetting == Setting.UtilityChargeCurrent) <button type="button" class="btn btn-light d-inline m-1" @onclick="()=>SetSetting(Setting.UtilityChargeCurrent,settings.MaxACChargeCurrent)">
{ <span class="@Spinner(Button.MaxUtilityChargeCurrent)"></span>
<div class="spinner-border m-2"></div> <span class="@Hidden(Button.MaxUtilityChargeCurrent)">Save</span>
} </button>
else
{
<div class="dropdown p-1">
<button class="btn btn-secondary dropdown-toggle" type="button" id="gridcurrent" data-bs-toggle="dropdown">
@Sanitize(settings.MaxACChargeCurrent) A
</button>
<ul class="dropdown-menu dropdown-menu">
@if (chargeAmpereValues != null)
{
@foreach (var val in chargeAmpereValues!.UtilityAmpereValues)
{
<li>
<a class="dropdown-item @(settings.MaxACChargeCurrent == val ? "active" : "")"
@onclick="()=>SetSetting(Setting.UtilityChargeCurrent,val)">
@Sanitize(val) A
</a>
</li>
}
}
</ul>
</div>
}
</div> </div>
</div> </div>
@ -96,21 +53,21 @@
Output Source Priority: Output Source Priority:
</div> </div>
<div class="col-6 bg-secondary p-2"> <div class="col-6 bg-secondary p-2">
<button disabled="@isLoadingChargeValues" type="button" class="btn btn-light d-block m-2" @onclick="()=>SetOutputPriority(OutputPriority.SolarFirst)"> <button type="button" class="btn btn-light d-block m-2" @onclick="()=>SetOutputPriority(OutputPriority.SolarFirst)">
<span class="@Spinner(Button.OpSolarFirst)"></span> <span class="@Spinner(Button.OpSolarFirst)"></span>
<span class="@Hidden(Button.OpSolarFirst)">Solar First</span> <span class="@Hidden(Button.OpSolarFirst)">Solar First</span>
<span class="@Success(Button.OpSolarFirst, OutputPriority.SolarFirst, settings.OutputPriority)"></span> <span class="@Success(Button.OpSolarFirst, OutputPriority.SolarFirst, settings.OutputPriority)"></span>
</button> </button>
<button disabled="@isLoadingChargeValues" type="button" class="btn btn-light d-block m-2" @onclick="()=>SetOutputPriority(OutputPriority.SolarBatteryUtility)"> <button type="button" class="btn btn-light d-block m-2" @onclick="()=>SetOutputPriority(OutputPriority.SolarBatteryUtility)">
<span class="@Spinner(Button.OpSolarBatteryUtility)"></span> <span class="@Spinner(Button.OpSolarBatteryUtility)"></span>
<span class="@Hidden(Button.OpSolarBatteryUtility)">Solar > Battery > Utility</span> <span class="@Hidden(Button.OpSolarBatteryUtility)">Solar > Battery > Utility</span>
<span class="@Success(Button.OpSolarBatteryUtility, OutputPriority.SolarBatteryUtility, settings.OutputPriority)"></span> <span class="@Success(Button.OpSolarBatteryUtility, OutputPriority.SolarBatteryUtility, settings.OutputPriority)"></span>
</button> </button>
<button disabled="@isLoadingChargeValues" type="button" class="btn btn-light d-block m-2" @onclick="()=>SetOutputPriority(OutputPriority.UtilityFirst)"> <button type="button" class="btn btn-light d-block m-2" @onclick="()=>SetOutputPriority(OutputPriority.UtilityFirst)">
<span class="@Spinner(Button.OpUtilityFirst)"></span> <span class="@Spinner(Button.OpUtilityFirst)"></span>
<span class="@Hidden(Button.OpUtilityFirst)">Utility First</span> <span class="@Hidden(Button.OpUtilityFirst)">Utility First</span>
<span class="@Success(Button.OpUtilityFirst, OutputPriority.UtilityFirst, settings.OutputPriority)"></span> <span class="@Success(Button.OpUtilityFirst, OutputPriority.UtilityFirst, settings.OutputPriority)"></span>
</button> </button>
</div> </div>
</div> </div>
@ -119,22 +76,22 @@
Battery Charging Priority: Battery Charging Priority:
</div> </div>
<div class="col-6 bg-secondary p-2"> <div class="col-6 bg-secondary p-2">
<button disabled="@isLoadingChargeValues" type="button" class="btn btn-light d-block m-2" @onclick="()=>SetChargePriority(ChargePriority.OnlySolar)"> <button type="button" class="btn btn-light d-block m-2" @onclick="()=>SetChargePriority(ChargePriority.OnlySolar)">
<span class="@Spinner(Button.ChOnlySolar)"></span> <span class="@Spinner(Button.ChOnlySolar)"></span>
<span class="@Hidden(Button.ChOnlySolar)">Solar Only</span> <span class="@Hidden(Button.ChOnlySolar)">Solar Only</span>
<span class="@Success(Button.ChOnlySolar, ChargePriority.OnlySolar, settings.ChargePriority)"></span> <span class="@Success(Button.ChOnlySolar, ChargePriority.OnlySolar, settings.ChargePriority)"></span>
</button> </button>
<button disabled="@isLoadingChargeValues" type="button" class="btn btn-light d-block m-2" @onclick="()=>SetChargePriority(ChargePriority.SolarFirst)"> <button type="button" class="btn btn-light d-block m-2" @onclick="()=>SetChargePriority(ChargePriority.SolarFirst)">
<span class="@Spinner(Button.ChSolarFirst)"></span> <span class="@Spinner(Button.ChSolarFirst)"></span>
<span class="@Hidden(Button.ChSolarFirst)">Solar First</span> <span class="@Hidden(Button.ChSolarFirst)">Solar First</span>
<span class="@Success(Button.ChSolarFirst, ChargePriority.SolarFirst, settings.ChargePriority)"></span> <span class="@Success(Button.ChSolarFirst, ChargePriority.SolarFirst, settings.ChargePriority)"></span>
</button> </button>
<button disabled="@isLoadingChargeValues" type="button" class="btn btn-light d-block m-2" @onclick="()=>SetChargePriority(ChargePriority.SolarAndUtility)"> <button type="button" class="btn btn-light d-block m-2" @onclick="()=>SetChargePriority(ChargePriority.SolarAndUtility)">
<span class="@Spinner(Button.ChSolarAndUtility)"></span> <span class="@Spinner(Button.ChSolarAndUtility)"></span>
<span class="@Hidden(Button.ChSolarAndUtility)">Solar & Utility</span> <span class="@Hidden(Button.ChSolarAndUtility)">Solar & Utility</span>
<span class="@Success(Button.ChSolarAndUtility, ChargePriority.SolarAndUtility, settings.ChargePriority)"></span> <span class="@Success(Button.ChSolarAndUtility, ChargePriority.SolarAndUtility, settings.ChargePriority)"></span>
</button> </button>
<button disabled="@isLoadingChargeValues" type="button" class="btn btn-light d-block m-2" @onclick="()=>SetChargePriority(ChargePriority.UtilityFirst)"> <button type="button" class="btn btn-light d-block m-2" @onclick="()=>SetChargePriority(ChargePriority.UtilityFirst)">
<span class="@Spinner(Button.ChUtilityFirst)"></span> <span class="@Spinner(Button.ChUtilityFirst)"></span>
<span class="@Hidden(Button.ChUtilityFirst)">Utility First</span> <span class="@Hidden(Button.ChUtilityFirst)">Utility First</span>
<span class="@Success(Button.ChUtilityFirst, ChargePriority.UtilityFirst, settings.ChargePriority)"></span> <span class="@Success(Button.ChUtilityFirst, ChargePriority.UtilityFirst, settings.ChargePriority)"></span>
@ -142,7 +99,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="tab-pane fade" id="voltages" role="tabpanel"> <div class="tab-pane fade" id="voltages" role="tabpanel">
<div class="row border-primary bg-light px-3 mt-0"> <div class="row border-primary bg-light px-3 mt-0">
<div class="col-6 my-auto fw-bold"> <div class="col-6 my-auto fw-bold">
@ -151,7 +108,7 @@
<div class="col-6 bg-secondary p-1"> <div class="col-6 bg-secondary p-1">
<div class="row"> <div class="row">
<div> <div>
<input @bind-value=settings.BulkChargeVoltage class="form-control bg-light d-inline m-1" style="width:4rem;" type="text" maxlength="4" > <input @bind-value=settings.BulkChargeVoltage class="form-control bg-light d-inline m-1" style="width:4rem;" type="text" maxlength="4">
<button type="button" class="btn btn-light d-inline m-1" @onclick="()=>SetVoltage(Setting.BulkVoltage)"> <button type="button" class="btn btn-light d-inline m-1" @onclick="()=>SetVoltage(Setting.BulkVoltage)">
<span class="@Spinner(Button.BulkVoltage)"></span> <span class="@Spinner(Button.BulkVoltage)"></span>
<span class="@Hidden(Button.BulkVoltage)">Save</span> <span class="@Hidden(Button.BulkVoltage)">Save</span>
@ -183,7 +140,8 @@
<div class="col-6 bg-secondary p-1"> <div class="col-6 bg-secondary p-1">
<div class="row"> <div class="row">
<div> <div>
<input @bind-value=settings.DischargeCuttOffVoltage class="form-control bg-light d-inline m-1" style="width:4rem;" type="text" maxlength="4"> <input @bind-value=settings.DischargeCuttOffVoltage class="form-control bg-light d-inline m-1" style="width:4rem;" type="text"
maxlength="4">
<button type="button" class="btn btn-light d-inline m-1" @onclick="()=>SetVoltage(Setting.DischargeCutOff)"> <button type="button" class="btn btn-light d-inline m-1" @onclick="()=>SetVoltage(Setting.DischargeCutOff)">
<span class="@Spinner(Button.DischargeCutOff)"></span> <span class="@Spinner(Button.DischargeCutOff)"></span>
<span class="@Hidden(Button.DischargeCutOff)">Save</span> <span class="@Hidden(Button.DischargeCutOff)">Save</span>
@ -215,7 +173,8 @@
<div class="col-6 bg-secondary p-1"> <div class="col-6 bg-secondary p-1">
<div class="row"> <div class="row">
<div> <div>
<input @bind-value=settings.BackToBatteryVoltage class="form-control bg-light d-inline m-1" style="width:4rem;" type="text" maxlength="4"> <input @bind-value=settings.BackToBatteryVoltage class="form-control bg-light d-inline m-1" style="width:4rem;" type="text"
maxlength="4">
<button type="button" class="btn btn-light d-inline m-1" @onclick="()=>SetVoltage(Setting.BackToBattery)"> <button type="button" class="btn btn-light d-inline m-1" @onclick="()=>SetVoltage(Setting.BackToBattery)">
<span class="@Spinner(Button.BackToBattery)"></span> <span class="@Spinner(Button.BackToBattery)"></span>
<span class="@Hidden(Button.BackToBattery)">Save</span> <span class="@Hidden(Button.BackToBattery)">Save</span>
@ -225,7 +184,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="tab-pane fade" id="spec" role="tabpanel"> <div class="tab-pane fade" id="spec" role="tabpanel">
<div class="row border-primary bg-light px-3 mt-0"> <div class="row border-primary bg-light px-3 mt-0">
<div class="col-6 my-auto fw-bold"> <div class="col-6 my-auto fw-bold">
@ -236,7 +195,7 @@
<div style="width:6rem;"> <div style="width:6rem;">
<input type="number" class="form-control bg-light" @bind-value=settings.SystemSpec.PV_MaxCapacity> <input type="number" class="form-control bg-light" @bind-value=settings.SystemSpec.PV_MaxCapacity>
</div> </div>
<div class="col-1 fw-bolder fs-5 m-1 text-white">Watts</div> <div class="col-1 fw-bolder fs-5 m-1 text-white">Watts</div>
</div> </div>
</div> </div>
</div> </div>
@ -296,8 +255,8 @@
<div class="col-3"></div> <div class="col-3"></div>
<button type="button" class="btn btn-primary my-3 col-6" @onclick="UpdateUserSettings"> <button type="button" class="btn btn-primary my-3 col-6" @onclick="UpdateUserSettings">
<span class="@Spinner(Button.UpdateUserSettings)" style="width:1.2rem;height:1.2rem;"></span> <span class="@Spinner(Button.UpdateUserSettings)" style="width:1.2rem;height:1.2rem;"></span>
<span class="@Hidden(Button.UpdateUserSettings)">Update</span> <span class="@Hidden(Button.UpdateUserSettings)">Update</span>
</button> </button>
<div class="col-3"></div> <div class="col-3"></div>
</div> </div>
</div> </div>
@ -305,49 +264,14 @@
} }
@code{ @code{
private static ChargeAmpereValues? chargeAmpereValues;
private bool isLoadingChargeValues = false;
private CurrentSettings? settings; private CurrentSettings? settings;
private Button currentButton = Button.None; private Button currentButton = Button.None;
private bool isSuccess; private bool isSuccess;
private string inProgressSetting = "";
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
settings = await Http.GetFromJsonAsync<CurrentSettings>("api/settings/get-setting-values"); settings = await Http.GetFromJsonAsync<CurrentSettings>("api/settings/get-setting-values");
StateHasChanged(); StateHasChanged();
_ = Task.Run(async () =>
{
if (chargeAmpereValues is null)
{
isLoadingChargeValues = true;
StateHasChanged();
using var client = new HttpClient
{
BaseAddress = new Uri(Http.BaseAddress?.ToString() ?? "/"),
Timeout = TimeSpan.FromSeconds(10)
};
chargeAmpereValues = await client.GetFromJsonAsync<ChargeAmpereValues>("api/settings/get-charge-ampere-values");
// some inverters only seem to support one of the two commands over usb
if (chargeAmpereValues?.CombinedAmpereValues.Any() is true &&
chargeAmpereValues?.UtilityAmpereValues.Any() is false)
{
chargeAmpereValues.UtilityAmpereValues = chargeAmpereValues.CombinedAmpereValues;
}
if (chargeAmpereValues?.CombinedAmpereValues.Any() is false &&
chargeAmpereValues?.UtilityAmpereValues.Any() is true)
{
chargeAmpereValues.CombinedAmpereValues = chargeAmpereValues.UtilityAmpereValues;
}
isLoadingChargeValues = false;
StateHasChanged();
}
});
} }
private async Task SetChargePriority(string priority) private async Task SetChargePriority(string priority)
@ -358,23 +282,29 @@
{ {
case ChargePriority.OnlySolar: case ChargePriority.OnlySolar:
currentButton = Button.ChOnlySolar; currentButton = Button.ChOnlySolar;
break; break;
case ChargePriority.SolarFirst: case ChargePriority.SolarFirst:
currentButton = Button.ChSolarFirst; currentButton = Button.ChSolarFirst;
break; break;
case ChargePriority.SolarAndUtility: case ChargePriority.SolarAndUtility:
currentButton = Button.ChSolarAndUtility; currentButton = Button.ChSolarAndUtility;
break; break;
case ChargePriority.UtilityFirst: case ChargePriority.UtilityFirst:
currentButton = Button.ChUtilityFirst; currentButton = Button.ChUtilityFirst;
break; break;
default: default:
currentButton = Button.None; currentButton = Button.None;
break; break;
}; }
;
if (await Http.GetStringAsync($"api/settings/set-setting/{Setting.ChargePriority}/{priority}") == "true") if (await Http.GetStringAsync($"api/settings/set-setting/{Setting.ChargePriority}/{priority}") == "true")
{ {
isSuccess = true; isSuccess = true;
UpdateLocalSetting(Setting.ChargePriority, priority); UpdateLocalSetting(Setting.ChargePriority, priority);
} }
@ -384,30 +314,22 @@
{ {
isSuccess = false; isSuccess = false;
switch (priority) currentButton = priority switch
{ {
case OutputPriority.SolarFirst: OutputPriority.SolarFirst => Button.OpSolarFirst,
currentButton = Button.OpSolarFirst; OutputPriority.SolarBatteryUtility => Button.OpSolarBatteryUtility,
break; OutputPriority.UtilityFirst => Button.OpUtilityFirst,
case OutputPriority.SolarBatteryUtility: _ => Button.None
currentButton = Button.OpSolarBatteryUtility;
break;
case OutputPriority.UtilityFirst:
currentButton = Button.OpUtilityFirst;
break;
default:
currentButton = Button.None;
break;
}; };
if (await Http.GetStringAsync($"api/settings/set-setting/{Setting.OutputPriority}/{priority}") == "true") if (await Http.GetStringAsync($"api/settings/set-setting/{Setting.OutputPriority}/{priority}") == "true")
{ {
isSuccess = true; isSuccess = true;
UpdateLocalSetting(Setting.OutputPriority,priority); UpdateLocalSetting(Setting.OutputPriority, priority);
} }
} }
private async Task SetVoltage(string setting) private async Task SetVoltage(Setting setting)
{ {
isSuccess = false; isSuccess = false;
decimal value = 0; decimal value = 0;
@ -417,27 +339,34 @@
case Setting.BulkVoltage: case Setting.BulkVoltage:
currentButton = Button.BulkVoltage; currentButton = Button.BulkVoltage;
value = settings!.BulkChargeVoltage; value = settings!.BulkChargeVoltage;
break; break;
case Setting.FloatVoltage: case Setting.FloatVoltage:
currentButton = Button.FloatVoltage; currentButton = Button.FloatVoltage;
value = settings!.FloatChargeVoltage; value = settings!.FloatChargeVoltage;
break; break;
case Setting.DischargeCutOff: case Setting.DischargeCutOff:
currentButton = Button.DischargeCutOff; currentButton = Button.DischargeCutOff;
value = settings!.DischargeCuttOffVoltage; value = settings!.DischargeCuttOffVoltage;
break; break;
case Setting.BackToGrid: case Setting.BackToGrid:
currentButton = Button.BackToGridVoltage; currentButton = Button.BackToGridVoltage;
value = settings!.BackToGridVoltage; value = settings!.BackToGridVoltage;
break; break;
case Setting.BackToBattery: case Setting.BackToBattery:
currentButton = Button.BackToBattery; currentButton = Button.BackToBattery;
value = settings!.BackToBatteryVoltage; value = settings!.BackToBatteryVoltage;
break; break;
default: default:
currentButton = Button.None; currentButton = Button.None;
break; break;
}; }
;
if (await Http.GetStringAsync($"api/settings/set-setting/{setting}/{value:00.0}") == "true") if (await Http.GetStringAsync($"api/settings/set-setting/{setting}/{value:00.0}") == "true")
{ {
@ -445,33 +374,33 @@
} }
} }
private async Task SetSetting(string settingName, string value) private async Task SetSetting(Setting settingName, string value)
{ {
inProgressSetting = settingName;
if (await Http.GetStringAsync($"api/settings/set-setting/{settingName}/{value}") == "true") if (await Http.GetStringAsync($"api/settings/set-setting/{settingName}/{value}") == "true")
{ {
UpdateLocalSetting(settingName, value); UpdateLocalSetting(settingName, value);
inProgressSetting = "";
} }
} }
private void UpdateLocalSetting(string settingName, string value) private void UpdateLocalSetting(Setting settingName, string value)
{ {
switch (settingName) switch (settingName)
{ {
case Setting.OutputPriority: case Setting.OutputPriority:
settings!.OutputPriority = value; settings!.OutputPriority = value;
break; break;
case Setting.ChargePriority: case Setting.ChargePriority:
settings!.ChargePriority = value; settings!.ChargePriority = value;
break; break;
case Setting.CombinedChargeCurrent: case Setting.CombinedChargeCurrent:
settings!.MaxCombinedChargeCurrent = value; settings!.MaxCombinedChargeCurrent = value;
break; break;
case Setting.UtilityChargeCurrent: case Setting.UtilityChargeCurrent:
settings!.MaxACChargeCurrent = value; settings!.MaxACChargeCurrent = value;
break;
default:
break; break;
} }
} }
@ -486,22 +415,22 @@
} }
private string Spinner(Button button) private string Spinner(Button button)
=> currentButton == button && !isSuccess => currentButton == button && !isSuccess
? "spinner-border" ? "spinner-border"
: ""; : "";
private string Hidden(Button button) private string Hidden(Button button)
=> currentButton == button && !isSuccess => currentButton == button && !isSuccess
? "visually-hidden" ? "visually-hidden"
: ""; : "";
private string Success(Button button, string currentValue, string settingValue) private string Success(Button button, string currentValue, string settingValue)
=> (currentButton == button && isSuccess) || currentValue == settingValue => (currentButton == button && isSuccess) || currentValue == settingValue
? "oi oi-circle-check text-success" ? "oi oi-circle-check text-success"
: ""; : "";
private string Sanitize(string value) private string Sanitize(string value)
=> value.StartsWith("0") ? value[1..] : value; => value.StartsWith("0") ? value[1..] : value;
private enum Button private enum Button
{ {
@ -515,23 +444,12 @@
OpSolarBatteryUtility = 7, OpSolarBatteryUtility = 7,
UpdateUserSettings = 8, UpdateUserSettings = 8,
BackToGridVoltage = 9, BackToGridVoltage = 9,
BackToBattery= 10, BackToBattery = 10,
DischargeCutOff = 11, DischargeCutOff = 11,
BulkVoltage = 12, BulkVoltage = 12,
FloatVoltage = 13 FloatVoltage = 13,
MaxCombinedChargeCurrent = 14,
MaxUtilityChargeCurrent = 15
} }
private static class Setting
{
public const string ChargePriority = "PCP";
public const string OutputPriority = "POP";
public const string CombinedChargeCurrent = "MNCHGC";
public const string UtilityChargeCurrent = "MUCHGC";
public const string BulkVoltage = "PCVV";
public const string FloatVoltage = "PBFT";
public const string DischargeCutOff = "PSDV";
public const string BackToGrid = "PBCV";
public const string BackToBattery = "PBDV";
}
} }

View File

@ -1,4 +1,4 @@
using InverterMon.Server.Persistance.Settings; using InverterMon.Server.Persistence.Settings;
using InverterMon.Shared.Models; using InverterMon.Shared.Models;
using SerialPortLib; using SerialPortLib;

View File

@ -1,12 +1,12 @@
using InverterMon.Server.InverterService; using InverterMon.Shared.Models;
using InverterMon.Shared.Models;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using InverterMon.Server.InverterService;
namespace InverterMon.Server.Endpoints.GetStatus; namespace InverterMon.Server.Endpoints.GetStatus;
public class Endpoint : EndpointWithoutRequest<object> public class Endpoint : EndpointWithoutRequest<object>
{ {
public CommandQueue Queue { get; set; } public CurrentStatus CurrentStatus { get; set; }
public override void Configure() public override void Configure()
{ {
@ -34,7 +34,7 @@ public class Endpoint : EndpointWithoutRequest<object>
{ {
if (Env.IsDevelopment()) if (Env.IsDevelopment())
{ {
var status = Queue.StatusCommand.Result; var status = CurrentStatus.Result;
status.OutputVoltage = Random.Shared.Next(240); status.OutputVoltage = Random.Shared.Next(240);
status.LoadWatts = Random.Shared.Next(3500); status.LoadWatts = Random.Shared.Next(3500);
status.LoadPercentage = Random.Shared.Next(100); status.LoadPercentage = Random.Shared.Next(100);
@ -51,11 +51,8 @@ public class Endpoint : EndpointWithoutRequest<object>
yield return status; yield return status;
} }
else else
{ yield return CurrentStatus.Result;
yield return Queue.IsAcceptingCommands
? Queue.StatusCommand.Result
: blank;
}
await Task.Delay(1000, c); await Task.Delay(1000, c);
} }
} }

View File

@ -1,5 +1,5 @@
using InverterMon.Server.Persistance; using InverterMon.Server.Persistence;
using InverterMon.Server.Persistance.Settings; using InverterMon.Server.Persistence.Settings;
using InverterMon.Shared.Models; using InverterMon.Shared.Models;
namespace InverterMon.Server.Endpoints.PVLog.GetPVForDay; namespace InverterMon.Server.Endpoints.PVLog.GetPVForDay;

View File

@ -1,53 +0,0 @@
using InverterMon.Server.InverterService;
using InverterMon.Server.Persistance.Settings;
using InverterMon.Shared.Models;
namespace InverterMon.Server.Endpoints.Settings.GetChargeAmpereValues;
public class Endpoint : EndpointWithoutRequest<ChargeAmpereValues>
{
public CommandQueue Queue { get; set; }
public UserSettings UserSettings { get; set; }
static ChargeAmpereValues? _ampereValues;
public override void Configure()
{
Get("settings/get-charge-ampere-values");
AllowAnonymous();
}
public override async Task HandleAsync(CancellationToken c)
{
if (Env.IsDevelopment())
{
await SendAsync(
new()
{
CombinedAmpereValues = new[] { "010", "020", "030" },
UtilityAmpereValues = new[] { "04", "10", "20" }
});
return;
}
if (_ampereValues is null)
{
var cmd1 = new InverterService.Commands.GetChargeAmpereValues(false);
var cmd2 = new InverterService.Commands.GetChargeAmpereValues(true);
Queue.AddCommands(cmd1, cmd2);
await Task.WhenAll(
cmd1.WhileProcessing(c, 5000),
cmd2.WhileProcessing(c, 5000));
_ampereValues = new()
{
CombinedAmpereValues = cmd1.Result,
UtilityAmpereValues = cmd2.Result
};
}
await SendAsync(_ampereValues);
}
}

View File

@ -1,13 +1,10 @@
using InverterMon.Server.InverterService; using InverterMon.Server.Persistence.Settings;
using InverterMon.Server.InverterService.Commands;
using InverterMon.Server.Persistance.Settings;
using InverterMon.Shared.Models; using InverterMon.Shared.Models;
namespace InverterMon.Server.Endpoints.Settings.GetSettingValues; namespace InverterMon.Server.Endpoints.Settings.GetSettingValues;
public class Endpoint : EndpointWithoutRequest<CurrentSettings> public class Endpoint : EndpointWithoutRequest<CurrentSettings>
{ {
public CommandQueue Queue { get; set; }
public UserSettings UserSettings { get; set; } public UserSettings UserSettings { get; set; }
public override void Configure() public override void Configure()
@ -18,27 +15,29 @@ public class Endpoint : EndpointWithoutRequest<CurrentSettings>
public override async Task HandleAsync(CancellationToken c) public override async Task HandleAsync(CancellationToken c)
{ {
var cmd = new GetSettings(); //todo: get values from inverter and send to client
cmd.Result.SystemSpec = UserSettings.ToSystemSpec();
if (Env.IsDevelopment()) // var cmd = new GetSettings();
{ // cmd.Result.SystemSpec = UserSettings.ToSystemSpec();
cmd.Result.ChargePriority = "03"; //
cmd.Result.MaxACChargeCurrent = "10"; // if (Env.IsDevelopment())
cmd.Result.MaxCombinedChargeCurrent = "020"; // {
cmd.Result.OutputPriority = "02"; // cmd.Result.ChargePriority = "03";
cmd.Result.BulkChargeVoltage = 27.1m; // cmd.Result.MaxACChargeCurrent = "10";
await SendAsync(cmd.Result); // cmd.Result.MaxCombinedChargeCurrent = "020";
return; // cmd.Result.OutputPriority = "02";
} // cmd.Result.BulkChargeVoltage = 27.1m;
// await SendAsync(cmd.Result);
Queue.AddCommands(cmd); // return;
// }
await cmd.WhileProcessing(c); //
// Queue.AddCommands(cmd);
if (cmd.IsComplete) //
await SendAsync(cmd.Result); // await cmd.WhileProcessing(c);
else //
ThrowError("Unable to read settings in a timely manner!"); // if (cmd.IsComplete)
// await SendAsync(cmd.Result);
// else
// ThrowError("Unable to read settings in a timely manner!");
} }
} }

View File

@ -1,11 +1,7 @@
using InverterMon.Server.InverterService; namespace InverterMon.Server.Endpoints.Settings.SetSettingValue;
namespace InverterMon.Server.Endpoints.Settings.SetSettingValue;
public class Endpoint : Endpoint<Shared.Models.SetSetting, bool> public class Endpoint : Endpoint<Shared.Models.SetSetting, bool>
{ {
public CommandQueue Queue { get; set; }
public override void Configure() public override void Configure()
{ {
Get("settings/set-setting/{Command}/{Value}"); Get("settings/set-setting/{Command}/{Value}");
@ -14,9 +10,11 @@ public class Endpoint : Endpoint<Shared.Models.SetSetting, bool>
public override async Task HandleAsync(Shared.Models.SetSetting r, CancellationToken c) public override async Task HandleAsync(Shared.Models.SetSetting r, CancellationToken c)
{ {
var cmd = new InverterService.Commands.SetSetting(r.Command, r.Value); //todo: set settings using inveter
Queue.AddCommands(cmd);
await cmd.WhileProcessing(c); // var cmd = new InverterService.Commands.SetSetting(r.Command, r.Value);
await SendAsync(cmd.Result); // Queue.AddCommands(cmd);
// await cmd.WhileProcessing(c);
// await SendAsync(cmd.Result);
} }
} }

View File

@ -1,5 +1,5 @@
using InverterMon.Server.Persistance; using InverterMon.Server.Persistence;
using InverterMon.Server.Persistance.Settings; using InverterMon.Server.Persistence.Settings;
namespace InverterMon.Server.Endpoints.Settings.SetSystemSpec; namespace InverterMon.Server.Endpoints.Settings.SetSystemSpec;

View File

@ -1,102 +0,0 @@
using System.Diagnostics;
using ICommand = InverterMon.Server.InverterService.Commands.ICommand;
namespace InverterMon.Server.InverterService;
class CommandExecutor : BackgroundService
{
readonly CommandQueue queue;
readonly ILogger<CommandExecutor> logger;
readonly string _devPath = "/dev/hidraw0";
readonly bool _isTroubleMode;
readonly string _mppPath = "/usr/local/bin/mpp-solar";
public CommandExecutor(CommandQueue queue, IConfiguration config, ILogger<CommandExecutor> log)
{
this.queue = queue;
logger = log;
_devPath = config["LaunchSettings:DeviceAddress"] ?? _devPath;
_isTroubleMode = config["LaunchSettings:TroubleMode"] == "yes";
_mppPath = config["LaunchSettings:MppSolarPath"] ?? _mppPath;
log.LogInformation("connecting to the inverter...");
var sw = new Stopwatch();
sw.Start();
while (!Connect() && sw.Elapsed.TotalMinutes <= 5)
Thread.Sleep(10000);
if (sw.Elapsed.TotalMinutes >= 5)
log.LogInformation("inverter connecting timed out!");
}
bool Connect()
{
if (!Inverter.Connect(_devPath, logger))
return false;
logger.LogInformation("connected to inverter at: [{adr}]", _devPath);
return true;
}
protected override async Task ExecuteAsync(CancellationToken ct)
{
var delay = 0;
var timeout = TimeSpan.FromMinutes(5);
while (!ct.IsCancellationRequested && delay <= timeout.TotalMilliseconds)
{
var cmd = queue.GetCommand();
if (cmd is not null)
{
try
{
await ExecuteCommand(cmd, ct);
queue.IsAcceptingCommands = true;
delay = 0;
queue.RemoveCommand();
}
catch (Exception x)
{
queue.IsAcceptingCommands = false;
logger.LogError("command [{cmd}] failed with reason [{msg}]", cmd.CommandString, x.Message);
await Task.Delay(delay += 1000);
}
}
else
await Task.Delay(500, ct);
}
logger.LogError("command execution halted due to excessive failures!");
}
async Task ExecuteCommand(ICommand command, CancellationToken ct)
{
if (_isTroubleMode && command.IsTroublesomeCmd)
{
Inverter.Disconnect();
using var process = new Process();
process.StartInfo.FileName = _mppPath;
process.StartInfo.Arguments = $"-p {_devPath} -o raw -c {command.CommandString}";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
command.Start();
var output = await process.StandardOutput.ReadToEndAsync(ct);
var result = output.ParseCli()[1..^1];
command.Parse(result);
command.End();
await process.WaitForExitAsync(ct);
Inverter.Connect(_devPath, logger);
}
else
{
command.Start();
await Inverter.Write(command.CommandString, ct);
command.Parse(await Inverter.Read(ct));
command.End();
}
}
}

View File

@ -1,32 +0,0 @@
using System.Collections.Concurrent;
using InverterMon.Server.InverterService.Commands;
using ICommand = InverterMon.Server.InverterService.Commands.ICommand;
namespace InverterMon.Server.InverterService;
public class CommandQueue
{
public bool IsAcceptingCommands { get; set; } = true;
public GetStatus StatusCommand { get; } = new();
readonly ConcurrentQueue<ICommand> _toProcess = new();
public bool AddCommands(params ICommand[] commands)
{
if (IsAcceptingCommands)
{
foreach (var cmd in commands)
_toProcess.Enqueue(cmd);
return true;
}
return false;
}
public ICommand? GetCommand()
=> _toProcess.TryPeek(out var command) ? command : null;
public void RemoveCommand()
=> _toProcess.TryDequeue(out _);
}

View File

@ -1,39 +0,0 @@
// ReSharper disable UnassignedGetOnlyAutoProperty
namespace InverterMon.Server.InverterService.Commands;
public interface ICommand
{
string CommandString { get; set; }
bool IsTroublesomeCmd { get; }
void Parse(string rawResponse);
void Start();
void End();
}
public abstract class Command<TResponseDto> : ICommand where TResponseDto : new()
{
public abstract string CommandString { get; set; }
public virtual bool IsTroublesomeCmd { get; }
public TResponseDto Result { get; protected set; } = new();
public bool IsComplete { get; private set; }
public abstract void Parse(string responseFromInverter);
protected DateTime startTime = DateTime.Now;
public void Start()
{
startTime = DateTime.Now;
IsComplete = false;
}
public void End()
=> IsComplete = true;
public async Task WhileProcessing(CancellationToken c, int timeoutMillis = Constants.StatusPollingFrequencyMillis)
{
while (!c.IsCancellationRequested && !IsComplete && DateTime.Now.Subtract(startTime).TotalMilliseconds <= timeoutMillis)
await Task.Delay(500, c);
}
}

View File

@ -1,32 +0,0 @@
// ReSharper disable VirtualMemberCallInConstructor
namespace InverterMon.Server.InverterService.Commands;
class GetChargeAmpereValues : Command<List<string>>
{
public override string CommandString { get; set; } = "QMCHGCR";
public override bool IsTroublesomeCmd { get; } = true;
public GetChargeAmpereValues(bool getUtilityValues)
{
Result.AddRange(new[] { "000" });
if (getUtilityValues)
CommandString = "QMUCHGCR";
}
public override void Parse(string responseFromInverter)
{
if (responseFromInverter.StartsWith("(NAK"))
return;
var parts = responseFromInverter[1..]
.Split(' ', StringSplitOptions.RemoveEmptyEntries)
.Select(x => x[..3]);
if (parts.Any())
Result.Clear(); //remove default values
Result.AddRange(parts);
}
}

View File

@ -1,52 +0,0 @@
using InverterMon.Shared.Models;
namespace InverterMon.Server.InverterService.Commands;
class GetSettings : Command<CurrentSettings>
{
public override string CommandString { get; set; } = "QPIRI";
public override void Parse(string responseFromInverter)
{
// 1) 230.0 - grid rating voltage
// 2) 15.2 - grid rating current
// 3) 230.0 - ac output rating voltage
// 4) 50.0 - ac output rating frequency
// 5) 15.2 - ac output rating current
// 6) 3500 - ac output rating apparant power
// 7) 3500 - ac output rating active power
// 8) 24.0 - batt rating voltage
// 9) 23.5 - batt back to grid voltage
// 10) 23.4 - batt discharge cut off voltage
// 11) 28.8 - batt bulk charging voltage
// 12) 27.0 - batt float charging voltage
// 13) 2 - battery type (0:agm / 1:flooded / 2: user)
// 14) 10 - max ac charging current
// 15) 020 - max combined charging current
// 16) 1 - input voltage range (0:appliance / 1:ups)
// 17) 1 - output source priority (0:utility first / 1:solar first / 2:solar>battery>utility)
// 18) 3 - charge priority (0:utility first /1:solar first / 2:solar & utility / 3:only solar)
// 19) 1 - parallel max number
// 20) 01 - machine type
// 21) 0 - topology
// 22) 0 - output mode
// 23) 28.5 - back to battery use voltage
// 24) 0 - pv ok for parallel
// 25) 1 - pv power balance
if (responseFromInverter.StartsWith("(NAK"))
return;
var parts = responseFromInverter[1..].Split(' ', StringSplitOptions.RemoveEmptyEntries);
Result.BackToGridVoltage = decimal.Parse(parts[9 - 1]);
Result.DischargeCuttOffVoltage = decimal.Parse(parts[10 - 1]);
Result.BulkChargeVoltage = decimal.Parse(parts[11 - 1]);
Result.FloatChargeVoltage = decimal.Parse(parts[12 - 1]);
Result.MaxACChargeCurrent = parts[14 - 1];
Result.MaxCombinedChargeCurrent = parts[15 - 1];
Result.OutputPriority = $"0{parts[17 - 1]}";
Result.ChargePriority = $"0{parts[18 - 1]}";
Result.BackToBatteryVoltage = decimal.Parse(parts[23 - 1]);
}
}

View File

@ -1,31 +0,0 @@
using InverterMon.Shared.Models;
namespace InverterMon.Server.InverterService.Commands;
public class GetStatus : Command<InverterStatus>
{
public override string CommandString { get; set; } = "QPIGS";
public override void Parse(string responseFromInverter)
{
//(232.0 50.1 232.0 50.1 0000 0000 000 476 27.02 000 100 0553 0000 000.0 27.00 00000 10011101 03 04 00000 101a\xc8\r
//(000.0 00.0 229.8 50.0 0851 0701 023 355 26.20 000 050 0041 00.0 058.5 00.00 00031 00010000 00 00 00000 010 0 01 0000
if (responseFromInverter.StartsWith("(NAK"))
return;
var parts = responseFromInverter[1..].Split(' ', StringSplitOptions.RemoveEmptyEntries);
Result.GridVoltage = decimal.Parse(parts[0]);
Result.OutputVoltage = decimal.Parse(parts[2]);
Result.LoadWatts = int.Parse(parts[5]);
Result.LoadPercentage = decimal.Parse(parts[6]);
Result.BatteryVoltage = decimal.Parse(parts[8]);
Result.BatteryChargeCurrent = int.Parse(parts[9]);
Result.HeatSinkTemperature = int.Parse(parts[11]);
Result.PVInputCurrent = decimal.Parse(parts[12]);
Result.PVInputVoltage = decimal.Parse(parts[13]);
Result.BatteryDischargeCurrent = int.Parse(parts[15]);
Result.PVInputWatt = Result.PVInputVoltage == 00 ? 0 : Convert.ToInt32(int.Parse(parts[19]));
}
}

View File

@ -1,19 +0,0 @@
// ReSharper disable VirtualMemberCallInConstructor
namespace InverterMon.Server.InverterService.Commands;
class SetSetting : Command<bool>
{
public override string CommandString { get; set; }
public override bool IsTroublesomeCmd { get; } = true;
public SetSetting(string settingName, string settingValue)
{
CommandString = settingName + settingValue;
}
public override void Parse(string responseFromInverter)
{
Result = responseFromInverter[1..4] == "ACK";
}
}

View File

@ -1,6 +0,0 @@
namespace InverterMon.Server.InverterService;
public static class Constants
{
public const int StatusPollingFrequencyMillis = 2000;
}

View File

@ -0,0 +1,8 @@
using InverterMon.Shared.Models;
namespace InverterMon.Server.InverterService;
public class CurrentStatus
{
public InverterStatus Result { get; set; }
}

View File

@ -1,28 +0,0 @@
using System.Text.RegularExpressions;
namespace InverterMon.Server.InverterService;
public static partial class Extensions
{
[GeneratedRegex(@"[^\u0009\u000A\u000D\u0020-\u007E]")]
private static partial Regex StringSanitizer();
static readonly Regex _sanRx = StringSanitizer();
public static string Sanitize(this string input)
=> _sanRx.Replace(input, "");
[GeneratedRegex(@"'\((.*?)\\")]
private static partial Regex CLIParser();
static readonly Regex _cliRx = CLIParser();
public static string ParseCli(this string input)
{
var match = _cliRx.Match(input);
return match.Success
? match.Groups[0].Value
: "`(NAK\\";
}
}

View File

@ -1,143 +0,0 @@
using System.IO.Ports;
using System.Text;
namespace InverterMon.Server.InverterService;
public static class Inverter
{
static SerialPort? _serialPort;
static FileStream? _fileStream;
public static bool Connect(string devicePath, ILogger logger)
{
try
{
if (devicePath.Contains("/hidraw", StringComparison.OrdinalIgnoreCase))
{
_fileStream = new(devicePath, FileMode.Open, FileAccess.ReadWrite);
return true;
}
if (devicePath.Contains("/ttyUSB", StringComparison.OrdinalIgnoreCase) || devicePath.Contains("COM", StringComparison.OrdinalIgnoreCase))
{
_serialPort = new(devicePath)
{
BaudRate = 2400,
Parity = Parity.None,
DataBits = 8,
StopBits = StopBits.One,
Handshake = Handshake.None
};
_serialPort.Open();
return true;
}
logger.LogError("device path [{path}] is not acceptable!", devicePath);
}
catch (Exception x)
{
logger.LogError("connection error at [{path}]. reason: [{reason}]", devicePath, x.Message);
}
return false;
}
public static void Disconnect()
{
_serialPort?.Close();
_serialPort?.Dispose();
_fileStream?.Close();
_fileStream?.Dispose();
}
static readonly byte[] _writeBuffer = new byte[512];
public static Task Write(string command, CancellationToken ct)
{
var cmdBytes = Encoding.ASCII.GetBytes(command);
var crc = CalculateXmodemCrc16(command);
Buffer.BlockCopy(cmdBytes, 0, _writeBuffer, 0, cmdBytes.Length);
_writeBuffer[cmdBytes.Length] = (byte)(crc >> 8);
_writeBuffer[cmdBytes.Length + 1] = (byte)(crc & 0xff);
_writeBuffer[cmdBytes.Length + 2] = 0x0d;
if (_fileStream != null)
return _fileStream.WriteAsync(_writeBuffer, 0, cmdBytes.Length + 3, ct);
return _serialPort != null
? _serialPort.BaseStream.WriteAsync(_writeBuffer, 0, cmdBytes.Length + 3, ct)
: Task.CompletedTask;
}
static readonly byte[] _readBuffer = new byte[1024];
public static async Task<string> Read(CancellationToken ct)
{
var pos = 0;
const byte eol = 0x0d;
if (_fileStream != null)
{
do
{
var readCount = await _fileStream.ReadAsync(_readBuffer.AsMemory(pos, _readBuffer.Length - pos), ct);
if (readCount > 0)
{
pos += readCount;
for (var i = pos - readCount; i < pos; i++)
{
if (_readBuffer[i] == eol)
return Encoding.ASCII.GetString(_readBuffer, 0, i - 2).Sanitize();
}
}
} while (pos < _readBuffer.Length);
}
else if (_serialPort != null)
{
do
{
var readCount = await _serialPort.BaseStream.ReadAsync(_readBuffer.AsMemory(pos, _readBuffer.Length - pos), ct);
if (readCount > 0)
{
pos += readCount;
for (var i = pos - readCount; i < pos; i++)
{
if (_readBuffer[i] == eol)
return Encoding.ASCII.GetString(_readBuffer, 0, i - 2).Sanitize();
}
}
} while (pos < _readBuffer.Length);
}
else
throw new InvalidOperationException("inverter not connected.");
throw new InvalidOperationException("buffer overflow.");
}
static ushort CalculateXmodemCrc16(string data)
{
ushort crc = 0;
var length = data.Length;
for (var i = 0; i < length; i++)
{
crc ^= (ushort)(data[i] << 8);
for (var j = 0; j < 8; j++)
{
if ((crc & 0x8000) != 0)
crc = (ushort)((crc << 1) ^ 0x1021);
else
crc <<= 1;
}
}
return crc;
}
}

View File

@ -1,26 +1,22 @@
using InverterMon.Server.Persistance; using InverterMon.Server.Persistence;
using InverterMon.Server.Persistance.Settings; using InverterMon.Server.Persistence.Settings;
namespace InverterMon.Server.InverterService; namespace InverterMon.Server.InverterService;
class StatusRetriever(CommandQueue queue, Database db, UserSettings userSettings) : BackgroundService class StatusRetriever(Database db, CurrentStatus currentStatus, UserSettings userSettings) : BackgroundService
{ {
protected override async Task ExecuteAsync(CancellationToken c) protected override async Task ExecuteAsync(CancellationToken c)
{ {
var cmd = queue.StatusCommand;
while (!c.IsCancellationRequested) while (!c.IsCancellationRequested)
{ {
if (queue.IsAcceptingCommands) currentStatus.Result.BatteryCapacity = userSettings.BatteryCapacity;
{ currentStatus.Result.PV_MaxCapacity = userSettings.PV_MaxCapacity;
//feels hacky. find a better solution.
cmd.Result.BatteryCapacity = userSettings.BatteryCapacity;
cmd.Result.PV_MaxCapacity = userSettings.PV_MaxCapacity;
queue.AddCommands(cmd); //todo: get data from inverter and map to CurrentStatus.Result
_ = db.UpdateTodaysPvGeneration(cmd, c);
} _ = db.UpdateTodaysPvGeneration(currentStatus, c);
await Task.Delay(Constants.StatusPollingFrequencyMillis);
await Task.Delay(2000);
} }
} }
} }

View File

@ -1,23 +1,22 @@
using InverterMon.Server.InverterService; using InverterMon.Server.InverterService;
using InverterMon.Server.InverterService.Commands; using InverterMon.Server.Persistence.PVGen;
using InverterMon.Server.Persistance.PVGen; using InverterMon.Server.Persistence.Settings;
using InverterMon.Server.Persistance.Settings;
using LiteDB; using LiteDB;
namespace InverterMon.Server.Persistance; namespace InverterMon.Server.Persistence;
public class Database public class Database
{ {
readonly LiteDatabase _db; readonly LiteDatabase _db;
readonly CommandQueue _queue; readonly CurrentStatus _currentStatus;
readonly UserSettings _settings; readonly UserSettings _settings;
readonly ILiteCollection<PVGeneration> _pvGenCollection; readonly ILiteCollection<PVGeneration> _pvGenCollection;
readonly ILiteCollection<UserSettings> _usrSettingsCollection; readonly ILiteCollection<UserSettings> _usrSettingsCollection;
PVGeneration? _today; PVGeneration? _today;
public Database(IHostApplicationLifetime lifetime, CommandQueue queue, UserSettings settings) public Database(IHostApplicationLifetime lifetime, CurrentStatus status, UserSettings settings)
{ {
_queue = queue; _currentStatus = status;
_settings = settings; _settings = settings;
_db = new("InverterMon.db") { CheckpointSize = 0 }; _db = new("InverterMon.db") { CheckpointSize = 0 };
lifetime.ApplicationStopping.Register(() => _db?.Dispose()); lifetime.ApplicationStopping.Register(() => _db?.Dispose());
@ -27,8 +26,6 @@ public class Database
RestoreUserSettings(); RestoreUserSettings();
} }
//todo: break apart this class and put seperated logic in each vertical slice
public void RestoreTodaysPvWattHours() public void RestoreTodaysPvWattHours()
{ {
var todayDayNumber = DateOnly.FromDateTime(DateTime.Now).DayNumber; var todayDayNumber = DateOnly.FromDateTime(DateTime.Now).DayNumber;
@ -39,26 +36,24 @@ public class Database
.SingleOrDefault(); .SingleOrDefault();
if (_today is not null) if (_today is not null)
_queue.StatusCommand.Result.RestorePVWattHours(_today.TotalWattHours); _currentStatus.Result.RestorePVWattHours(_today.TotalWattHours);
else else
{ {
_today = new() { Id = todayDayNumber }; _today = new() { Id = todayDayNumber };
_today.SetTotalWattHours(0); _today.SetTotalWattHours(0);
_queue.StatusCommand.Result.RestorePVWattHours(0); _currentStatus.Result.RestorePVWattHours(0);
_pvGenCollection.Insert(_today); _pvGenCollection.Insert(_today);
_db.Checkpoint(); _db.Checkpoint();
} }
} }
public async Task UpdateTodaysPvGeneration(GetStatus cmd, CancellationToken c) public async Task UpdateTodaysPvGeneration(CurrentStatus cmd, CancellationToken c)
{ {
var hourNow = DateTime.Now.Hour; var hourNow = DateTime.Now.Hour;
if (hourNow < _settings.SunlightStartHour || hourNow >= _settings.SunlightEndHour) if (hourNow < _settings.SunlightStartHour || hourNow >= _settings.SunlightEndHour)
return; return;
await cmd.WhileProcessing(c);
var todayDayNumber = DateOnly.FromDateTime(DateTime.Now).DayNumber; var todayDayNumber = DateOnly.FromDateTime(DateTime.Now).DayNumber;
if (_today?.Id == todayDayNumber) if (_today?.Id == todayDayNumber)

View File

@ -1,4 +1,4 @@
namespace InverterMon.Server.Persistance.PVGen; namespace InverterMon.Server.Persistence.PVGen;
public static class PVGenExtensions public static class PVGenExtensions
{ {

View File

@ -1,4 +1,4 @@
namespace InverterMon.Server.Persistance.PVGen; namespace InverterMon.Server.Persistence.PVGen;
public class PVGeneration public class PVGeneration
{ {

View File

@ -1,7 +1,7 @@
using InverterMon.Server.Persistance.PVGen; using InverterMon.Server.Persistence.PVGen;
using InverterMon.Shared.Models; using InverterMon.Shared.Models;
namespace InverterMon.Server.Persistance.Settings; namespace InverterMon.Server.Persistence.Settings;
public class UserSettings public class UserSettings
{ {

View File

@ -4,8 +4,8 @@ using System.Net;
using InverterMon.Server; using InverterMon.Server;
using InverterMon.Server.BatteryService; using InverterMon.Server.BatteryService;
using InverterMon.Server.InverterService; using InverterMon.Server.InverterService;
using InverterMon.Server.Persistance; using InverterMon.Server.Persistence;
using InverterMon.Server.Persistance.Settings; using InverterMon.Server.Persistence.Settings;
//avoid parsing issues with non-english cultures //avoid parsing issues with non-english cultures
var cultureInfo = new CultureInfo("en-US"); var cultureInfo = new CultureInfo("en-US");
@ -18,15 +18,14 @@ _ = int.TryParse(bld.Configuration["LaunchSettings:WebPort"] ?? "80", out var po
bld.WebHost.ConfigureKestrel(o => o.Listen(IPAddress.Any, port)); bld.WebHost.ConfigureKestrel(o => o.Listen(IPAddress.Any, port));
bld.Services bld.Services
.AddSingleton<CurrentStatus>()
.AddSingleton<UserSettings>() .AddSingleton<UserSettings>()
.AddSingleton<CommandQueue>()
.AddSingleton<Database>() .AddSingleton<Database>()
.AddSingleton<JkBms>(); .AddSingleton<JkBms>();
if (!bld.Environment.IsDevelopment()) if (!bld.Environment.IsDevelopment())
{ {
bld.Services bld.Services
.AddHostedService<CommandExecutor>()
.AddHostedService<StatusRetriever>(); .AddHostedService<StatusRetriever>();
} }

View File

@ -1,7 +0,0 @@
namespace InverterMon.Shared.Models;
public class ChargeAmpereValues
{
public IEnumerable<string> CombinedAmpereValues { get; set; }
public IEnumerable<string> UtilityAmpereValues { get; set; }
}

View File

@ -2,8 +2,8 @@
public static class ChargePriority public static class ChargePriority
{ {
public const string SolarFirst = "01"; public const string SolarFirst = "1";
public const string SolarAndUtility = "02"; public const string SolarAndUtility = "2";
public const string OnlySolar = "03"; public const string OnlySolar = "3";
public const string UtilityFirst = "00"; public const string UtilityFirst = "0";
} }

View File

@ -0,0 +1,14 @@
namespace InverterMon.Shared.Models;
public enum Setting
{
ChargePriority = 1,
OutputPriority = 2,
CombinedChargeCurrent = 3,
UtilityChargeCurrent = 4,
BulkVoltage = 5,
FloatVoltage = 6,
DischargeCutOff = 7,
BackToGrid = 8,
BackToBattery = 9
}

View File

@ -2,7 +2,7 @@
public static class OutputPriority public static class OutputPriority
{ {
public const string SolarFirst = "01"; public const string SolarFirst = "1";
public const string SolarBatteryUtility = "02"; public const string SolarBatteryUtility = "2";
public const string UtilityFirst = "00"; public const string UtilityFirst = "0";
} }