inverter testing passed

This commit is contained in:
djnitehawk 2025-03-13 10:12:31 +05:30 committed by Dĵ ΝιΓΞΗΛψΚ
parent d498e8624a
commit 80caa1c631
15 changed files with 135 additions and 97 deletions

View File

@ -62,8 +62,8 @@
</div>
<div class="col">
<div class="row h-100 align-content-center">
<span class="fs-5 @TemperatureCss()">
@(status?.HeatSinkTemperature) C°
<span class="fs-6 text-muted">
@(status?.WorkingMode)
</span>
</div>
</div>
@ -205,16 +205,6 @@
private static double RoundToOneDecimal(double? val)
=> Math.Round(val ?? 0, 1);
private static string TemperatureCss()
{
return status?.HeatSinkTemperature switch
{
>= 55 and < 65 => "text-danger",
>= 65 => "text-danger fw-bolder blinktext",
_ => "text-muted"
};
}
public static async Task StartStatusStreaming(string basePath)
{
//note: only reason we have a full-time stream download is because there's a bug in

View File

@ -375,11 +375,11 @@
}
}
private async Task SetChargeCurrent(Setting settingName, byte value)
private async Task SetChargeCurrent(Setting setting, byte value)
{
isSuccess = false;
switch (settingName)
switch (setting)
{
case Setting.CombinedChargeCurrent:
currentButton = Button.MaxCombinedChargeCurrent;
@ -392,10 +392,12 @@
break;
}
if (await Http.GetStringAsync($"api/settings/set-setting/{settingName}/{value}") == "true")
var xxx = $"api/settings/set-setting/{setting}/{value}";
if (await Http.GetStringAsync($"api/settings/set-setting/{setting}/{value}") == "true")
{
isSuccess = true;
UpdateLocalSetting(settingName, value);
UpdateLocalSetting(setting, value);
}
}

View File

@ -7,6 +7,7 @@ namespace InverterMon.Server.Endpoints.GetStatus;
public class Endpoint : EndpointWithoutRequest<object>
{
public FelicitySolarInverter Inverter { get; set; } = null!;
public IHostApplicationLifetime AppLife { get; set; } = null!;
public override void Configure()
{
@ -28,32 +29,32 @@ public class Endpoint : EndpointWithoutRequest<object>
async IAsyncEnumerable<InverterStatus> GetDataStream([EnumeratorCancellation] CancellationToken c)
{
while (!c.IsCancellationRequested)
while (!c.IsCancellationRequested && !AppLife.ApplicationStopping.IsCancellationRequested)
{
if (Env.IsDevelopment())
{
var status = new InverterStatus
{
OutputVoltage = Random.Shared.Next(240),
LoadWatts = Random.Shared.Next(3500),
LoadPercentage = Random.Shared.Next(100),
BatteryVoltage = Random.Shared.Next(24),
BatteryChargeCurrent = Random.Shared.Next(20),
BatteryDischargeCurrent = Random.Shared.Next(300),
HeatSinkTemperature = Random.Shared.Next(300),
PVInputCurrent = Random.Shared.Next(300),
PVInputVoltage = Random.Shared.Next(300),
PVInputWatt = Random.Shared.Next(1000),
PV_MaxCapacity = 1000,
BatteryCapacity = 100
};
// if (Env.IsDevelopment())
// {
// var status = new InverterStatus
// {
// OutputVoltage = Random.Shared.Next(240),
// LoadWatts = Random.Shared.Next(3500),
// LoadPercentage = Random.Shared.Next(100),
// BatteryVoltage = Random.Shared.Next(24),
// BatteryChargeCurrent = Random.Shared.Next(20),
// BatteryDischargeCurrent = Random.Shared.Next(300),
// HeatSinkTemperature = Random.Shared.Next(300),
// PVInputCurrent = Random.Shared.Next(300),
// PVInputVoltage = Random.Shared.Next(300),
// PVInputWatt = Random.Shared.Next(1000),
// PV_MaxCapacity = 1000,
// BatteryCapacity = 100
// };
//
// yield return status;
// }
// else
yield return Inverter.Status;
yield return status;
}
else
yield return Inverter.Status;
await Task.Delay(1000, c);
await Task.Delay(2000, c);
}
}
}

View File

@ -17,25 +17,25 @@ public class Endpoint : EndpointWithoutRequest<CurrentSettings>
public override async Task HandleAsync(CancellationToken c)
{
if (Env.IsDevelopment())
{
var res = new CurrentSettings
{
BackToBatteryVoltage = 48.1,
BackToGridVoltage = 48.2,
FloatChargeVoltage = 48.3,
ChargePriority = ChargePriority.OnlySolar,
DischargeCuttOffVoltage = 48.4,
BulkChargeVoltage = 48.5,
MaxACChargeCurrent = 10,
MaxCombinedChargeCurrent = 20,
OutputPriority = OutputPriority.SolarFirst,
SystemSpec = UserSettings.ToSystemSpec()
};
await SendAsync(res, cancellation: c);
return;
}
// if (Env.IsDevelopment())
// {
// var res = new CurrentSettings
// {
// BackToBatteryVoltage = 48.1,
// BackToGridVoltage = 48.2,
// FloatChargeVoltage = 48.3,
// ChargePriority = ChargePriority.OnlySolar,
// DischargeCuttOffVoltage = 48.4,
// BulkChargeVoltage = 48.5,
// MaxACChargeCurrent = 10,
// MaxCombinedChargeCurrent = 20,
// OutputPriority = OutputPriority.SolarFirst,
// SystemSpec = UserSettings.ToSystemSpec()
// };
// await SendAsync(res, cancellation: c);
//
// return;
// }
try
{

View File

@ -14,16 +14,17 @@ public class Endpoint : Endpoint<Shared.Models.SetSetting, bool>
public override async Task HandleAsync(Shared.Models.SetSetting r, CancellationToken c)
{
if (Env.IsDevelopment())
{
await SendAsync(true, cancellation: c);
return;
}
// if (Env.IsDevelopment())
// {
// await SendAsync(true, cancellation: c);
//
// return;
// }
try
{
Inverter.SetSetting(r.Setting, r.Value);
await SendAsync(true, cancellation: c);
}
catch
{

View File

@ -57,19 +57,39 @@ public sealed class FelicitySolarInverter
{
var regs = ReadRegisters(StatusStartAddress, StatusRegisterCount);
// WorkingMode = regs[0], // 0x1101: Working mode (offset 0)
lock (_lock)
{
if (!_serialPort.IsOpen)
return;
}
// BatteryChargingStage = regs[1], // 0x1102: Battery charging stage (offset 1)
Status.BatteryVoltage = regs[7] / 100.0; // 0x1108: Battery voltage (offset 0x1108 - 0x1101 = 7)
Status.BatteryDischargeCurrent = regs[8]; // 0x1109: Battery current (offset 8) -- signed value
Status.BatteryChargeCurrent = regs[8]; // 0x1109: Battery current (offset 8) -- signed value
Status.BatteryDischargeWatts = regs[9]; // 0x110A: Battery power (offset 9) -- signed value
Status.BatteryChargeWatts = regs[9]; // 0x110A: Battery power (offset 9) -- signed value
Status.OutputVoltage = regs[16] / 10.0; // 0x1111: AC output voltage (offset 0x1111 - 0x1101 = 16)
Status.LoadWatts = regs[29]; // 0x111E: AC output active power (offset 0x111E - 0x1101 = 29)
Status.LoadPercentage = regs[31]; // 0x1120: Load percentage (offset 0x1120 - 0x1101 = 31)
Status.PVInputVoltage = regs[37] / 10.0; // 0x1126: PV input voltage (offset 0x1126 - 0x1101 = 37)
Status.PVInputWatt = regs[41]; // 0x112A: PV input power (offset 0x112A - 0x1101 = 41) -- signed value
Status.WorkingMode = (WorkingMode)regs[0]; // 0x1101: Working mode (offset 0)
Status.BatteryVoltage = regs[7] / 100.0; // 0x1108: Battery voltage (offset 0x1108 - 0x1101 = 7)
var disCur = ChargeStatus(regs[8]); // 0x1109: Battery current (offset 8) -- signed value
Status.BatteryDischargeCurrent = disCur.IsDischarge ? disCur.PositiveValue : 0;
Status.BatteryChargeCurrent = disCur.IsDischarge is false ? disCur.PositiveValue : 0;
var disPow = ChargeStatus(regs[9]); // 0x110A: Battery power (offset 9) -- signed value
Status.BatteryDischargeWatts = disPow.IsDischarge ? disPow.PositiveValue : 0;
Status.BatteryChargeWatts = disPow.IsDischarge is false ? disPow.PositiveValue : 0;
Status.OutputVoltage = regs[16] / 10.0; // 0x1111: AC output voltage (offset 0x1111 - 0x1101 = 16)
Status.GridVoltage = regs[22] / 10.0; // 0x1111: AC output voltage (offset 0x1117 - 0x1101 = 22)
Status.LoadWatts = regs[29]; // 0x111E: AC output active power (offset 0x111E - 0x1101 = 29)
Status.LoadPercentage = regs[31]; // 0x1120: Load percentage (offset 0x1120 - 0x1101 = 31)
Status.PVInputVoltage = regs[37] / 10.0; // 0x1126: PV input voltage (offset 0x1126 - 0x1101 = 37)
Status.PVInputWatt = regs[41]; // 0x112A: PV input power (offset 0x112A - 0x1101 = 41) -- signed value
static (bool IsDischarge, short PositiveValue) ChargeStatus(short value)
{
var isNegative = value < 0;
var positiveValue = isNegative ? (short)-value : value;
return (isNegative, positiveValue);
}
}
// The settings registers we need are located between 0x211F and 0x2159.
@ -206,6 +226,9 @@ public sealed class FelicitySolarInverter
var response = SendModbusRequest(frame);
if (response.Length == 0)
return [];
// Expected response structure:
// [Slave Address][Function Code][Byte Count][Data...][CRC Lo][CRC Hi]
@ -228,6 +251,9 @@ public sealed class FelicitySolarInverter
{
lock (_lock) //prevent concurrent access
{
if (!_serialPort.IsOpen)
return [];
_serialPort.DiscardInBuffer();
_serialPort.DiscardOutBuffer();

View File

@ -22,9 +22,11 @@ class StatusRetriever(
await Task.Delay(5000);
}
log.LogInformation("Connected to inverter at device address: [{port}]", port);
appLife.ApplicationStopping.Register(inverter.Close);
while (!c.IsCancellationRequested)
while (!c.IsCancellationRequested && !appLife.ApplicationStopping.IsCancellationRequested)
{
inverter.Status.BatteryCapacity = userSettings.BatteryCapacity;
inverter.Status.PV_MaxCapacity = userSettings.PV_MaxCapacity;
@ -41,9 +43,16 @@ class StatusRetriever(
continue;
}
db.UpdateTodaysPvGeneration(c);
try
{
db.UpdateTodaysPvGeneration(c);
}
catch
{
//do nothing
}
await Task.Delay(2000);
await Task.Delay(2000, CancellationToken.None);
}
}
}

View File

@ -8,7 +8,7 @@ public class UserSettings
public int Id { get; set; } = 1;
public int PV_MaxCapacity { get; set; } = 1000;
public int BatteryCapacity { get; set; } = 100;
public float BatteryNominalVoltage { get; set; } = 25.6f;
public double BatteryNominalVoltage { get; set; } = 25.6f;
public int SunlightStartHour { get; set; } = 6;
public int SunlightEndHour { get; set; } = 18;
public int[] PVGraphRange => new[] { 0, (SunlightEndHour - SunlightStartHour) * 60 };

View File

@ -23,11 +23,12 @@ bld.Services
.AddSingleton<FelicitySolarInverter>()
.AddSingleton<JkBms>();
if (!bld.Environment.IsDevelopment())
{
bld.Services
.AddHostedService<StatusRetriever>();
}
// if (!bld.Environment.IsDevelopment())
// {
bld.Services
.AddHostedService<StatusRetriever>();
// }
bld.Services.AddFastEndpoints(o => o.SourceGeneratorDiscoveredTypes = DiscoveredTypes.All);

View File

@ -5,10 +5,10 @@
"dotnetRunMessages": true,
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "http://localhost:80;https://localhost:443",
"applicationUrl": "http://localhost:80",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
}

View File

@ -1,15 +1,12 @@
{
"LaunchSettings": {
"DeviceAddress": "/dev/ttyUSB1",
"DeviceAddress": "COM3",
"JkBmsAddress": "/dev/ttyUSB0",
"WebPort": 80
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Debug",
"Microsoft.Hosting.Lifetime": "Debug",
"FastEndpoints.StartupTimer": "Debug"
"Default": "Information"
}
}
}

View File

@ -65,7 +65,7 @@ public class BMSStatus
public double AvgPowerWatts => Math.Round(AvgCurrentAmps * PackVoltage, 0, MidpointRounding.AwayFromZero);
[JsonPropertyName("u")]
public float PackNominalVoltage { get; set; }
public double PackNominalVoltage { get; set; }
public string GetTimeString()
{

View File

@ -11,4 +11,15 @@ public enum Setting
DischargeCutOff = 7,
BackToGrid = 8,
BackToBattery = 9
}
public enum WorkingMode
{
POWER = 0,
STANDBY = 1,
BYPASS = 2,
BATTERY = 3,
FAULT = 4,
LINE = 5,
CHARGING = 6
}

View File

@ -38,7 +38,7 @@ public class InverterStatus
public double GridVoltage { get; set; }
[JsonPropertyName("l")]
public int HeatSinkTemperature { get; set; }
public WorkingMode WorkingMode { get; set; }
[JsonPropertyName("m")]
public double LoadCurrent => LoadWatts == 0 ? 0 : LoadWatts / OutputVoltage;
@ -69,7 +69,7 @@ public class InverterStatus
pvInputWatt = value;
var interval = (DateTime.Now - pvInputWattHourLastComputed).TotalSeconds;
PVInputWattHour += value / (3600 / Convert.ToDouble(interval));
PVInputWattHour += value / (3600 / interval);
pvInputWattHourLastComputed = DateTime.Now;
}
}
@ -81,7 +81,7 @@ public class InverterStatus
public int PV_MaxCapacity { get; set; }
[JsonPropertyName("v")]
public int PVPotential => PVInputWatt > 0 ? Convert.ToInt32(Convert.ToDouble(PVInputWatt) / PV_MaxCapacity * 100) : 0;
public int PVPotential => PVInputWatt > 0 ? Convert.ToInt32(PVInputWatt / PV_MaxCapacity * 100) : 0;
int pvInputWatt;
DateTime pvInputWattHourLastComputed;

View File

@ -4,7 +4,7 @@ public class SystemSpec
{
public int PV_MaxCapacity { get; set; } = 1000;
public int BatteryCapacity { get; set; } = 100;
public float BatteryNominalVoltage { get; set; } = 25.6f;
public double BatteryNominalVoltage { get; set; } = 25.6f;
public int SunlightStartHour { get; set; } = 6;
public int SunlightEndHour { get; set; } = 18;
}