wip: before inverter service
This commit is contained in:
parent
806ceefe4b
commit
7fca1d1cfb
@ -6,7 +6,7 @@
|
||||
|
||||
<Loader Enabled=@(settings is null)/>
|
||||
|
||||
@if(settings is not null)
|
||||
@if (settings is not null)
|
||||
{
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
@ -26,33 +26,12 @@
|
||||
Max Combined Charge Current:
|
||||
</div>
|
||||
<div class="col-6 p-2 bg-secondary">
|
||||
|
||||
@if (chargeAmpereValues == null || inProgressSetting == Setting.CombinedChargeCurrent)
|
||||
{
|
||||
<div class="spinner-border m-2"></div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<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>
|
||||
}
|
||||
<input @bind-value=settings.MaxCombinedChargeCurrent 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="()=>SetSetting(Setting.CombinedChargeCurrent,settings.MaxCombinedChargeCurrent)">
|
||||
<span class="@Spinner(Button.MaxCombinedChargeCurrent)"></span>
|
||||
<span class="@Hidden(Button.MaxCombinedChargeCurrent)">Save</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -61,33 +40,11 @@
|
||||
Max Grid Charge Current:
|
||||
</div>
|
||||
<div class="col-6 p-2 bg-secondary">
|
||||
|
||||
@if (chargeAmpereValues == null || inProgressSetting == Setting.UtilityChargeCurrent)
|
||||
{
|
||||
<div class="spinner-border m-2"></div>
|
||||
}
|
||||
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>
|
||||
}
|
||||
<input @bind-value=settings.MaxACChargeCurrent 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="()=>SetSetting(Setting.UtilityChargeCurrent,settings.MaxACChargeCurrent)">
|
||||
<span class="@Spinner(Button.MaxUtilityChargeCurrent)"></span>
|
||||
<span class="@Hidden(Button.MaxUtilityChargeCurrent)">Save</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -96,17 +53,17 @@
|
||||
Output Source Priority:
|
||||
</div>
|
||||
<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="@Hidden(Button.OpSolarFirst)">Solar First</span>
|
||||
<span class="@Success(Button.OpSolarFirst, OutputPriority.SolarFirst, settings.OutputPriority)"></span>
|
||||
</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="@Hidden(Button.OpSolarBatteryUtility)">Solar > Battery > Utility</span>
|
||||
<span class="@Success(Button.OpSolarBatteryUtility, OutputPriority.SolarBatteryUtility, settings.OutputPriority)"></span>
|
||||
</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="@Hidden(Button.OpUtilityFirst)">Utility First</span>
|
||||
<span class="@Success(Button.OpUtilityFirst, OutputPriority.UtilityFirst, settings.OutputPriority)"></span>
|
||||
@ -119,22 +76,22 @@
|
||||
Battery Charging Priority:
|
||||
</div>
|
||||
<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="@Hidden(Button.ChOnlySolar)">Solar Only</span>
|
||||
<span class="@Success(Button.ChOnlySolar, ChargePriority.OnlySolar, settings.ChargePriority)"></span>
|
||||
</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="@Hidden(Button.ChSolarFirst)">Solar First</span>
|
||||
<span class="@Success(Button.ChSolarFirst, ChargePriority.SolarFirst, settings.ChargePriority)"></span>
|
||||
</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="@Hidden(Button.ChSolarAndUtility)">Solar & Utility</span>
|
||||
<span class="@Success(Button.ChSolarAndUtility, ChargePriority.SolarAndUtility, settings.ChargePriority)"></span>
|
||||
</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="@Hidden(Button.ChUtilityFirst)">Utility First</span>
|
||||
<span class="@Success(Button.ChUtilityFirst, ChargePriority.UtilityFirst, settings.ChargePriority)"></span>
|
||||
@ -151,7 +108,7 @@
|
||||
<div class="col-6 bg-secondary p-1">
|
||||
<div class="row">
|
||||
<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)">
|
||||
<span class="@Spinner(Button.BulkVoltage)"></span>
|
||||
<span class="@Hidden(Button.BulkVoltage)">Save</span>
|
||||
@ -183,7 +140,8 @@
|
||||
<div class="col-6 bg-secondary p-1">
|
||||
<div class="row">
|
||||
<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)">
|
||||
<span class="@Spinner(Button.DischargeCutOff)"></span>
|
||||
<span class="@Hidden(Button.DischargeCutOff)">Save</span>
|
||||
@ -215,7 +173,8 @@
|
||||
<div class="col-6 bg-secondary p-1">
|
||||
<div class="row">
|
||||
<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)">
|
||||
<span class="@Spinner(Button.BackToBattery)"></span>
|
||||
<span class="@Hidden(Button.BackToBattery)">Save</span>
|
||||
@ -236,7 +195,7 @@
|
||||
<div style="width:6rem;">
|
||||
<input type="number" class="form-control bg-light" @bind-value=settings.SystemSpec.PV_MaxCapacity>
|
||||
</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>
|
||||
@ -305,49 +264,14 @@
|
||||
}
|
||||
|
||||
@code{
|
||||
private static ChargeAmpereValues? chargeAmpereValues;
|
||||
private bool isLoadingChargeValues = false;
|
||||
private CurrentSettings? settings;
|
||||
private Button currentButton = Button.None;
|
||||
private bool isSuccess;
|
||||
private string inProgressSetting = "";
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
settings = await Http.GetFromJsonAsync<CurrentSettings>("api/settings/get-setting-values");
|
||||
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)
|
||||
@ -358,20 +282,26 @@
|
||||
{
|
||||
case ChargePriority.OnlySolar:
|
||||
currentButton = Button.ChOnlySolar;
|
||||
|
||||
break;
|
||||
case ChargePriority.SolarFirst:
|
||||
currentButton = Button.ChSolarFirst;
|
||||
|
||||
break;
|
||||
case ChargePriority.SolarAndUtility:
|
||||
currentButton = Button.ChSolarAndUtility;
|
||||
|
||||
break;
|
||||
case ChargePriority.UtilityFirst:
|
||||
currentButton = Button.ChUtilityFirst;
|
||||
|
||||
break;
|
||||
default:
|
||||
currentButton = Button.None;
|
||||
|
||||
break;
|
||||
};
|
||||
}
|
||||
;
|
||||
|
||||
if (await Http.GetStringAsync($"api/settings/set-setting/{Setting.ChargePriority}/{priority}") == "true")
|
||||
{
|
||||
@ -384,30 +314,22 @@
|
||||
{
|
||||
isSuccess = false;
|
||||
|
||||
switch (priority)
|
||||
currentButton = priority switch
|
||||
{
|
||||
case OutputPriority.SolarFirst:
|
||||
currentButton = Button.OpSolarFirst;
|
||||
break;
|
||||
case OutputPriority.SolarBatteryUtility:
|
||||
currentButton = Button.OpSolarBatteryUtility;
|
||||
break;
|
||||
case OutputPriority.UtilityFirst:
|
||||
currentButton = Button.OpUtilityFirst;
|
||||
break;
|
||||
default:
|
||||
currentButton = Button.None;
|
||||
break;
|
||||
OutputPriority.SolarFirst => Button.OpSolarFirst,
|
||||
OutputPriority.SolarBatteryUtility => Button.OpSolarBatteryUtility,
|
||||
OutputPriority.UtilityFirst => Button.OpUtilityFirst,
|
||||
_ => Button.None
|
||||
};
|
||||
|
||||
if (await Http.GetStringAsync($"api/settings/set-setting/{Setting.OutputPriority}/{priority}") == "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;
|
||||
decimal value = 0;
|
||||
@ -417,27 +339,34 @@
|
||||
case Setting.BulkVoltage:
|
||||
currentButton = Button.BulkVoltage;
|
||||
value = settings!.BulkChargeVoltage;
|
||||
|
||||
break;
|
||||
case Setting.FloatVoltage:
|
||||
currentButton = Button.FloatVoltage;
|
||||
value = settings!.FloatChargeVoltage;
|
||||
|
||||
break;
|
||||
case Setting.DischargeCutOff:
|
||||
currentButton = Button.DischargeCutOff;
|
||||
value = settings!.DischargeCuttOffVoltage;
|
||||
|
||||
break;
|
||||
case Setting.BackToGrid:
|
||||
currentButton = Button.BackToGridVoltage;
|
||||
value = settings!.BackToGridVoltage;
|
||||
|
||||
break;
|
||||
case Setting.BackToBattery:
|
||||
currentButton = Button.BackToBattery;
|
||||
value = settings!.BackToBatteryVoltage;
|
||||
|
||||
break;
|
||||
default:
|
||||
currentButton = Button.None;
|
||||
|
||||
break;
|
||||
};
|
||||
}
|
||||
;
|
||||
|
||||
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")
|
||||
{
|
||||
UpdateLocalSetting(settingName, value);
|
||||
inProgressSetting = "";
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLocalSetting(string settingName, string value)
|
||||
private void UpdateLocalSetting(Setting settingName, string value)
|
||||
{
|
||||
switch (settingName)
|
||||
{
|
||||
case Setting.OutputPriority:
|
||||
settings!.OutputPriority = value;
|
||||
|
||||
break;
|
||||
case Setting.ChargePriority:
|
||||
settings!.ChargePriority = value;
|
||||
|
||||
break;
|
||||
case Setting.CombinedChargeCurrent:
|
||||
settings!.MaxCombinedChargeCurrent = value;
|
||||
|
||||
break;
|
||||
case Setting.UtilityChargeCurrent:
|
||||
settings!.MaxACChargeCurrent = value;
|
||||
break;
|
||||
default:
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -487,18 +416,18 @@
|
||||
|
||||
private string Spinner(Button button)
|
||||
=> currentButton == button && !isSuccess
|
||||
? "spinner-border"
|
||||
: "";
|
||||
? "spinner-border"
|
||||
: "";
|
||||
|
||||
private string Hidden(Button button)
|
||||
=> currentButton == button && !isSuccess
|
||||
? "visually-hidden"
|
||||
: "";
|
||||
? "visually-hidden"
|
||||
: "";
|
||||
|
||||
private string Success(Button button, string currentValue, string settingValue)
|
||||
=> (currentButton == button && isSuccess) || currentValue == settingValue
|
||||
? "oi oi-circle-check text-success"
|
||||
: "";
|
||||
? "oi oi-circle-check text-success"
|
||||
: "";
|
||||
|
||||
private string Sanitize(string value)
|
||||
=> value.StartsWith("0") ? value[1..] : value;
|
||||
@ -515,23 +444,12 @@
|
||||
OpSolarBatteryUtility = 7,
|
||||
UpdateUserSettings = 8,
|
||||
BackToGridVoltage = 9,
|
||||
BackToBattery= 10,
|
||||
BackToBattery = 10,
|
||||
DischargeCutOff = 11,
|
||||
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";
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
using InverterMon.Server.Persistance.Settings;
|
||||
using InverterMon.Server.Persistence.Settings;
|
||||
using InverterMon.Shared.Models;
|
||||
using SerialPortLib;
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
using InverterMon.Server.InverterService;
|
||||
using InverterMon.Shared.Models;
|
||||
using InverterMon.Shared.Models;
|
||||
using System.Runtime.CompilerServices;
|
||||
using InverterMon.Server.InverterService;
|
||||
|
||||
namespace InverterMon.Server.Endpoints.GetStatus;
|
||||
|
||||
public class Endpoint : EndpointWithoutRequest<object>
|
||||
{
|
||||
public CommandQueue Queue { get; set; }
|
||||
public CurrentStatus CurrentStatus { get; set; }
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
@ -34,7 +34,7 @@ public class Endpoint : EndpointWithoutRequest<object>
|
||||
{
|
||||
if (Env.IsDevelopment())
|
||||
{
|
||||
var status = Queue.StatusCommand.Result;
|
||||
var status = CurrentStatus.Result;
|
||||
status.OutputVoltage = Random.Shared.Next(240);
|
||||
status.LoadWatts = Random.Shared.Next(3500);
|
||||
status.LoadPercentage = Random.Shared.Next(100);
|
||||
@ -51,11 +51,8 @@ public class Endpoint : EndpointWithoutRequest<object>
|
||||
yield return status;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return Queue.IsAcceptingCommands
|
||||
? Queue.StatusCommand.Result
|
||||
: blank;
|
||||
}
|
||||
yield return CurrentStatus.Result;
|
||||
|
||||
await Task.Delay(1000, c);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
using InverterMon.Server.Persistance;
|
||||
using InverterMon.Server.Persistance.Settings;
|
||||
using InverterMon.Server.Persistence;
|
||||
using InverterMon.Server.Persistence.Settings;
|
||||
using InverterMon.Shared.Models;
|
||||
|
||||
namespace InverterMon.Server.Endpoints.PVLog.GetPVForDay;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -1,13 +1,10 @@
|
||||
using InverterMon.Server.InverterService;
|
||||
using InverterMon.Server.InverterService.Commands;
|
||||
using InverterMon.Server.Persistance.Settings;
|
||||
using InverterMon.Server.Persistence.Settings;
|
||||
using InverterMon.Shared.Models;
|
||||
|
||||
namespace InverterMon.Server.Endpoints.Settings.GetSettingValues;
|
||||
|
||||
public class Endpoint : EndpointWithoutRequest<CurrentSettings>
|
||||
{
|
||||
public CommandQueue Queue { get; set; }
|
||||
public UserSettings UserSettings { get; set; }
|
||||
|
||||
public override void Configure()
|
||||
@ -18,27 +15,29 @@ public class Endpoint : EndpointWithoutRequest<CurrentSettings>
|
||||
|
||||
public override async Task HandleAsync(CancellationToken c)
|
||||
{
|
||||
var cmd = new GetSettings();
|
||||
cmd.Result.SystemSpec = UserSettings.ToSystemSpec();
|
||||
//todo: get values from inverter and send to client
|
||||
|
||||
if (Env.IsDevelopment())
|
||||
{
|
||||
cmd.Result.ChargePriority = "03";
|
||||
cmd.Result.MaxACChargeCurrent = "10";
|
||||
cmd.Result.MaxCombinedChargeCurrent = "020";
|
||||
cmd.Result.OutputPriority = "02";
|
||||
cmd.Result.BulkChargeVoltage = 27.1m;
|
||||
await SendAsync(cmd.Result);
|
||||
return;
|
||||
}
|
||||
|
||||
Queue.AddCommands(cmd);
|
||||
|
||||
await cmd.WhileProcessing(c);
|
||||
|
||||
if (cmd.IsComplete)
|
||||
await SendAsync(cmd.Result);
|
||||
else
|
||||
ThrowError("Unable to read settings in a timely manner!");
|
||||
// var cmd = new GetSettings();
|
||||
// cmd.Result.SystemSpec = UserSettings.ToSystemSpec();
|
||||
//
|
||||
// if (Env.IsDevelopment())
|
||||
// {
|
||||
// cmd.Result.ChargePriority = "03";
|
||||
// cmd.Result.MaxACChargeCurrent = "10";
|
||||
// cmd.Result.MaxCombinedChargeCurrent = "020";
|
||||
// cmd.Result.OutputPriority = "02";
|
||||
// cmd.Result.BulkChargeVoltage = 27.1m;
|
||||
// await SendAsync(cmd.Result);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// Queue.AddCommands(cmd);
|
||||
//
|
||||
// await cmd.WhileProcessing(c);
|
||||
//
|
||||
// if (cmd.IsComplete)
|
||||
// await SendAsync(cmd.Result);
|
||||
// else
|
||||
// ThrowError("Unable to read settings in a timely manner!");
|
||||
}
|
||||
}
|
||||
@ -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 CommandQueue Queue { get; set; }
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
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)
|
||||
{
|
||||
var cmd = new InverterService.Commands.SetSetting(r.Command, r.Value);
|
||||
Queue.AddCommands(cmd);
|
||||
await cmd.WhileProcessing(c);
|
||||
await SendAsync(cmd.Result);
|
||||
//todo: set settings using inveter
|
||||
|
||||
// var cmd = new InverterService.Commands.SetSetting(r.Command, r.Value);
|
||||
// Queue.AddCommands(cmd);
|
||||
// await cmd.WhileProcessing(c);
|
||||
// await SendAsync(cmd.Result);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
using InverterMon.Server.Persistance;
|
||||
using InverterMon.Server.Persistance.Settings;
|
||||
using InverterMon.Server.Persistence;
|
||||
using InverterMon.Server.Persistence.Settings;
|
||||
|
||||
namespace InverterMon.Server.Endpoints.Settings.SetSystemSpec;
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 _);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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]);
|
||||
}
|
||||
}
|
||||
@ -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]));
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
namespace InverterMon.Server.InverterService;
|
||||
|
||||
public static class Constants
|
||||
{
|
||||
public const int StatusPollingFrequencyMillis = 2000;
|
||||
}
|
||||
8
src/Server/InverterService/CurrentStatus.cs
Normal file
8
src/Server/InverterService/CurrentStatus.cs
Normal file
@ -0,0 +1,8 @@
|
||||
using InverterMon.Shared.Models;
|
||||
|
||||
namespace InverterMon.Server.InverterService;
|
||||
|
||||
public class CurrentStatus
|
||||
{
|
||||
public InverterStatus Result { get; set; }
|
||||
}
|
||||
@ -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\\";
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -1,26 +1,22 @@
|
||||
using InverterMon.Server.Persistance;
|
||||
using InverterMon.Server.Persistance.Settings;
|
||||
using InverterMon.Server.Persistence;
|
||||
using InverterMon.Server.Persistence.Settings;
|
||||
|
||||
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)
|
||||
{
|
||||
var cmd = queue.StatusCommand;
|
||||
|
||||
while (!c.IsCancellationRequested)
|
||||
{
|
||||
if (queue.IsAcceptingCommands)
|
||||
{
|
||||
//feels hacky. find a better solution.
|
||||
cmd.Result.BatteryCapacity = userSettings.BatteryCapacity;
|
||||
cmd.Result.PV_MaxCapacity = userSettings.PV_MaxCapacity;
|
||||
currentStatus.Result.BatteryCapacity = userSettings.BatteryCapacity;
|
||||
currentStatus.Result.PV_MaxCapacity = userSettings.PV_MaxCapacity;
|
||||
|
||||
queue.AddCommands(cmd);
|
||||
_ = db.UpdateTodaysPvGeneration(cmd, c);
|
||||
}
|
||||
await Task.Delay(Constants.StatusPollingFrequencyMillis);
|
||||
//todo: get data from inverter and map to CurrentStatus.Result
|
||||
|
||||
_ = db.UpdateTodaysPvGeneration(currentStatus, c);
|
||||
|
||||
await Task.Delay(2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@ -1,23 +1,22 @@
|
||||
using InverterMon.Server.InverterService;
|
||||
using InverterMon.Server.InverterService.Commands;
|
||||
using InverterMon.Server.Persistance.PVGen;
|
||||
using InverterMon.Server.Persistance.Settings;
|
||||
using InverterMon.Server.Persistence.PVGen;
|
||||
using InverterMon.Server.Persistence.Settings;
|
||||
using LiteDB;
|
||||
|
||||
namespace InverterMon.Server.Persistance;
|
||||
namespace InverterMon.Server.Persistence;
|
||||
|
||||
public class Database
|
||||
{
|
||||
readonly LiteDatabase _db;
|
||||
readonly CommandQueue _queue;
|
||||
readonly CurrentStatus _currentStatus;
|
||||
readonly UserSettings _settings;
|
||||
readonly ILiteCollection<PVGeneration> _pvGenCollection;
|
||||
readonly ILiteCollection<UserSettings> _usrSettingsCollection;
|
||||
PVGeneration? _today;
|
||||
|
||||
public Database(IHostApplicationLifetime lifetime, CommandQueue queue, UserSettings settings)
|
||||
public Database(IHostApplicationLifetime lifetime, CurrentStatus status, UserSettings settings)
|
||||
{
|
||||
_queue = queue;
|
||||
_currentStatus = status;
|
||||
_settings = settings;
|
||||
_db = new("InverterMon.db") { CheckpointSize = 0 };
|
||||
lifetime.ApplicationStopping.Register(() => _db?.Dispose());
|
||||
@ -27,8 +26,6 @@ public class Database
|
||||
RestoreUserSettings();
|
||||
}
|
||||
|
||||
//todo: break apart this class and put seperated logic in each vertical slice
|
||||
|
||||
public void RestoreTodaysPvWattHours()
|
||||
{
|
||||
var todayDayNumber = DateOnly.FromDateTime(DateTime.Now).DayNumber;
|
||||
@ -39,26 +36,24 @@ public class Database
|
||||
.SingleOrDefault();
|
||||
|
||||
if (_today is not null)
|
||||
_queue.StatusCommand.Result.RestorePVWattHours(_today.TotalWattHours);
|
||||
_currentStatus.Result.RestorePVWattHours(_today.TotalWattHours);
|
||||
else
|
||||
{
|
||||
_today = new() { Id = todayDayNumber };
|
||||
_today.SetTotalWattHours(0);
|
||||
_queue.StatusCommand.Result.RestorePVWattHours(0);
|
||||
_currentStatus.Result.RestorePVWattHours(0);
|
||||
_pvGenCollection.Insert(_today);
|
||||
_db.Checkpoint();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateTodaysPvGeneration(GetStatus cmd, CancellationToken c)
|
||||
public async Task UpdateTodaysPvGeneration(CurrentStatus cmd, CancellationToken c)
|
||||
{
|
||||
var hourNow = DateTime.Now.Hour;
|
||||
|
||||
if (hourNow < _settings.SunlightStartHour || hourNow >= _settings.SunlightEndHour)
|
||||
return;
|
||||
|
||||
await cmd.WhileProcessing(c);
|
||||
|
||||
var todayDayNumber = DateOnly.FromDateTime(DateTime.Now).DayNumber;
|
||||
|
||||
if (_today?.Id == todayDayNumber)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
namespace InverterMon.Server.Persistance.PVGen;
|
||||
namespace InverterMon.Server.Persistence.PVGen;
|
||||
|
||||
public static class PVGenExtensions
|
||||
{
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
namespace InverterMon.Server.Persistance.PVGen;
|
||||
namespace InverterMon.Server.Persistence.PVGen;
|
||||
|
||||
public class PVGeneration
|
||||
{
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
using InverterMon.Server.Persistance.PVGen;
|
||||
using InverterMon.Server.Persistence.PVGen;
|
||||
using InverterMon.Shared.Models;
|
||||
|
||||
namespace InverterMon.Server.Persistance.Settings;
|
||||
namespace InverterMon.Server.Persistence.Settings;
|
||||
|
||||
public class UserSettings
|
||||
{
|
||||
|
||||
@ -4,8 +4,8 @@ using System.Net;
|
||||
using InverterMon.Server;
|
||||
using InverterMon.Server.BatteryService;
|
||||
using InverterMon.Server.InverterService;
|
||||
using InverterMon.Server.Persistance;
|
||||
using InverterMon.Server.Persistance.Settings;
|
||||
using InverterMon.Server.Persistence;
|
||||
using InverterMon.Server.Persistence.Settings;
|
||||
|
||||
//avoid parsing issues with non-english cultures
|
||||
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.Services
|
||||
.AddSingleton<CurrentStatus>()
|
||||
.AddSingleton<UserSettings>()
|
||||
.AddSingleton<CommandQueue>()
|
||||
.AddSingleton<Database>()
|
||||
.AddSingleton<JkBms>();
|
||||
|
||||
if (!bld.Environment.IsDevelopment())
|
||||
{
|
||||
bld.Services
|
||||
.AddHostedService<CommandExecutor>()
|
||||
.AddHostedService<StatusRetriever>();
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
namespace InverterMon.Shared.Models;
|
||||
|
||||
public class ChargeAmpereValues
|
||||
{
|
||||
public IEnumerable<string> CombinedAmpereValues { get; set; }
|
||||
public IEnumerable<string> UtilityAmpereValues { get; set; }
|
||||
}
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
public static class ChargePriority
|
||||
{
|
||||
public const string SolarFirst = "01";
|
||||
public const string SolarAndUtility = "02";
|
||||
public const string OnlySolar = "03";
|
||||
public const string UtilityFirst = "00";
|
||||
public const string SolarFirst = "1";
|
||||
public const string SolarAndUtility = "2";
|
||||
public const string OnlySolar = "3";
|
||||
public const string UtilityFirst = "0";
|
||||
}
|
||||
14
src/Shared/Models/InverterSetting.cs
Normal file
14
src/Shared/Models/InverterSetting.cs
Normal 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
|
||||
}
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
public static class OutputPriority
|
||||
{
|
||||
public const string SolarFirst = "01";
|
||||
public const string SolarBatteryUtility = "02";
|
||||
public const string UtilityFirst = "00";
|
||||
public const string SolarFirst = "1";
|
||||
public const string SolarBatteryUtility = "2";
|
||||
public const string UtilityFirst = "0";
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user