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>
<div class="col"> <div class="col">
<div class="row h-100 align-content-center"> <div class="row h-100 align-content-center">
<span class="fs-5 @TemperatureCss()"> <span class="fs-6 text-muted">
@(status?.HeatSinkTemperature) C° @(status?.WorkingMode)
</span> </span>
</div> </div>
</div> </div>
@ -205,16 +205,6 @@
private static double RoundToOneDecimal(double? val) private static double RoundToOneDecimal(double? val)
=> Math.Round(val ?? 0, 1); => 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) public static async Task StartStatusStreaming(string basePath)
{ {
//note: only reason we have a full-time stream download is because there's a bug in //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; isSuccess = false;
switch (settingName) switch (setting)
{ {
case Setting.CombinedChargeCurrent: case Setting.CombinedChargeCurrent:
currentButton = Button.MaxCombinedChargeCurrent; currentButton = Button.MaxCombinedChargeCurrent;
@ -392,10 +392,12 @@
break; 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; 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 class Endpoint : EndpointWithoutRequest<object>
{ {
public FelicitySolarInverter Inverter { get; set; } = null!; public FelicitySolarInverter Inverter { get; set; } = null!;
public IHostApplicationLifetime AppLife { get; set; } = null!;
public override void Configure() public override void Configure()
{ {
@ -28,32 +29,32 @@ public class Endpoint : EndpointWithoutRequest<object>
async IAsyncEnumerable<InverterStatus> GetDataStream([EnumeratorCancellation] CancellationToken c) async IAsyncEnumerable<InverterStatus> GetDataStream([EnumeratorCancellation] CancellationToken c)
{ {
while (!c.IsCancellationRequested) while (!c.IsCancellationRequested && !AppLife.ApplicationStopping.IsCancellationRequested)
{ {
if (Env.IsDevelopment()) // if (Env.IsDevelopment())
{ // {
var status = new InverterStatus // var status = new InverterStatus
{ // {
OutputVoltage = Random.Shared.Next(240), // OutputVoltage = Random.Shared.Next(240),
LoadWatts = Random.Shared.Next(3500), // LoadWatts = Random.Shared.Next(3500),
LoadPercentage = Random.Shared.Next(100), // LoadPercentage = Random.Shared.Next(100),
BatteryVoltage = Random.Shared.Next(24), // BatteryVoltage = Random.Shared.Next(24),
BatteryChargeCurrent = Random.Shared.Next(20), // BatteryChargeCurrent = Random.Shared.Next(20),
BatteryDischargeCurrent = Random.Shared.Next(300), // BatteryDischargeCurrent = Random.Shared.Next(300),
HeatSinkTemperature = Random.Shared.Next(300), // HeatSinkTemperature = Random.Shared.Next(300),
PVInputCurrent = Random.Shared.Next(300), // PVInputCurrent = Random.Shared.Next(300),
PVInputVoltage = Random.Shared.Next(300), // PVInputVoltage = Random.Shared.Next(300),
PVInputWatt = Random.Shared.Next(1000), // PVInputWatt = Random.Shared.Next(1000),
PV_MaxCapacity = 1000, // PV_MaxCapacity = 1000,
BatteryCapacity = 100 // BatteryCapacity = 100
}; // };
//
// yield return status;
// }
// else
yield return Inverter.Status;
yield return status; await Task.Delay(2000, c);
}
else
yield return Inverter.Status;
await Task.Delay(1000, c);
} }
} }
} }

View File

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

View File

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

View File

@ -22,9 +22,11 @@ class StatusRetriever(
await Task.Delay(5000); await Task.Delay(5000);
} }
log.LogInformation("Connected to inverter at device address: [{port}]", port);
appLife.ApplicationStopping.Register(inverter.Close); appLife.ApplicationStopping.Register(inverter.Close);
while (!c.IsCancellationRequested) while (!c.IsCancellationRequested && !appLife.ApplicationStopping.IsCancellationRequested)
{ {
inverter.Status.BatteryCapacity = userSettings.BatteryCapacity; inverter.Status.BatteryCapacity = userSettings.BatteryCapacity;
inverter.Status.PV_MaxCapacity = userSettings.PV_MaxCapacity; inverter.Status.PV_MaxCapacity = userSettings.PV_MaxCapacity;
@ -41,9 +43,16 @@ class StatusRetriever(
continue; 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 Id { get; set; } = 1;
public int PV_MaxCapacity { get; set; } = 1000; public int PV_MaxCapacity { get; set; } = 1000;
public int BatteryCapacity { get; set; } = 100; 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 SunlightStartHour { get; set; } = 6;
public int SunlightEndHour { get; set; } = 18; public int SunlightEndHour { get; set; } = 18;
public int[] PVGraphRange => new[] { 0, (SunlightEndHour - SunlightStartHour) * 60 }; public int[] PVGraphRange => new[] { 0, (SunlightEndHour - SunlightStartHour) * 60 };

View File

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

View File

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

View File

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

View File

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

View File

@ -12,3 +12,14 @@ public enum Setting
BackToGrid = 8, BackToGrid = 8,
BackToBattery = 9 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; } public double GridVoltage { get; set; }
[JsonPropertyName("l")] [JsonPropertyName("l")]
public int HeatSinkTemperature { get; set; } public WorkingMode WorkingMode { get; set; }
[JsonPropertyName("m")] [JsonPropertyName("m")]
public double LoadCurrent => LoadWatts == 0 ? 0 : LoadWatts / OutputVoltage; public double LoadCurrent => LoadWatts == 0 ? 0 : LoadWatts / OutputVoltage;
@ -69,7 +69,7 @@ public class InverterStatus
pvInputWatt = value; pvInputWatt = value;
var interval = (DateTime.Now - pvInputWattHourLastComputed).TotalSeconds; var interval = (DateTime.Now - pvInputWattHourLastComputed).TotalSeconds;
PVInputWattHour += value / (3600 / Convert.ToDouble(interval)); PVInputWattHour += value / (3600 / interval);
pvInputWattHourLastComputed = DateTime.Now; pvInputWattHourLastComputed = DateTime.Now;
} }
} }
@ -81,7 +81,7 @@ public class InverterStatus
public int PV_MaxCapacity { get; set; } public int PV_MaxCapacity { get; set; }
[JsonPropertyName("v")] [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; int pvInputWatt;
DateTime pvInputWattHourLastComputed; DateTime pvInputWattHourLastComputed;

View File

@ -4,7 +4,7 @@ public class SystemSpec
{ {
public int PV_MaxCapacity { get; set; } = 1000; public int PV_MaxCapacity { get; set; } = 1000;
public int BatteryCapacity { get; set; } = 100; 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 SunlightStartHour { get; set; } = 6;
public int SunlightEndHour { get; set; } = 18; public int SunlightEndHour { get; set; } = 18;
} }