wip: inverter service

This commit is contained in:
djnitehawk 2025-03-12 15:39:21 +05:30
parent 7fca1d1cfb
commit b642498a60
14 changed files with 376 additions and 64 deletions

View File

@ -45,7 +45,8 @@
<div class="container text-center fw-bold p-0"> <div class="container text-center fw-bold p-0">
<div class="row bg-light rounded"> <div class="row bg-light rounded">
<div class="progress p-0" style="height:2px;"> <div class="progress p-0" style="height:2px;">
<span class="progress-bar" role="progressbar" style="width: @RoundToWholeNumber(status?.LoadPercentage)%" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100"></span> <span class="progress-bar" role="progressbar" style="width: @RoundToWholeNumber(status?.LoadPercentage)%" aria-valuenow="25"
aria-valuemin="0" aria-valuemax="100"></span>
</div> </div>
<div class="col"> <div class="col">
<div class="fs-1 text-danger">@status?.LoadWatts</div> <div class="fs-1 text-danger">@status?.LoadWatts</div>
@ -86,7 +87,8 @@
<div class="container text-center fw-bold p-0"> <div class="container text-center fw-bold p-0">
<div class="row bg-light rounded"> <div class="row bg-light rounded">
<div class="progress p-0" style="height:2px;"> <div class="progress p-0" style="height:2px;">
<span class="progress-bar" role="progressbar" style="width: @status?.PVPotential%" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100"></span> <span class="progress-bar" role="progressbar" style="width: @status?.PVPotential%" aria-valuenow="25" aria-valuemin="0"
aria-valuemax="100"></span>
</div> </div>
<div class="col"> <div class="col">
<div class="fs-1 text-success">@status?.PVInputWatt</div> <div class="fs-1 text-success">@status?.PVInputWatt</div>
@ -124,7 +126,8 @@
<div class="container text-center m-0 p-0"> <div class="container text-center m-0 p-0">
<div class="row m-0 p-0"> <div class="row m-0 p-0">
<div class="progress p-0" style="height:2px;"> <div class="progress p-0" style="height:2px;">
<span class="progress-bar" role="progressbar" style="width: @status?.BatteryDischargePotential%" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100"></span> <span class="progress-bar" role="progressbar" style="width: @status?.BatteryDischargePotential%" aria-valuenow="25"
aria-valuemin="0" aria-valuemax="100"></span>
</div> </div>
</div> </div>
<div class="row mt-2"> <div class="row mt-2">
@ -196,10 +199,10 @@
onStatusRetrievalError -= NullifyStatus; onStatusRetrievalError -= NullifyStatus;
} }
private static decimal RoundToWholeNumber(decimal? val) private static double RoundToWholeNumber(double? val)
=> Math.Round(val ?? 0, 0); => Math.Round(val ?? 0, 0);
private static decimal RoundToOneDecimal(decimal? val) private static double RoundToOneDecimal(double? val)
=> Math.Round(val ?? 0, 1); => Math.Round(val ?? 0, 1);
private static string TemperatureCss() private static string TemperatureCss()
@ -259,7 +262,7 @@
} }
} }
private static decimal GetCRate() private static double GetCRate()
{ {
if (status?.BatteryChargeCRate > 0) if (status?.BatteryChargeCRate > 0)
return Math.Round(status.BatteryChargeCRate, 2); return Math.Round(status.BatteryChargeCRate, 2);

View File

@ -6,7 +6,7 @@ namespace InverterMon.Server.Endpoints.GetStatus;
public class Endpoint : EndpointWithoutRequest<object> public class Endpoint : EndpointWithoutRequest<object>
{ {
public CurrentStatus CurrentStatus { get; set; } public FelicitySolarInverter Inverter { 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 = CurrentStatus.Result; var status = new InverterStatus();
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,7 +51,7 @@ public class Endpoint : EndpointWithoutRequest<object>
yield return status; yield return status;
} }
else else
yield return CurrentStatus.Result; yield return Inverter.Status;
await Task.Delay(1000, c); await Task.Delay(1000, c);
} }

View File

@ -41,12 +41,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Remove="appsettings.Development.json"/>
<None Remove="InverterMon-log.db"/>
<None Remove="InverterMon.db"/>
<None Remove="InverterService\protocol.pdf"/>
<None Include="../changelog.md" Link="changelog.md"/> <None Include="../changelog.md" Link="changelog.md"/>
<None Remove="Properties\PublishProfiles\FolderProfile.pubxml"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

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

View File

@ -0,0 +1,325 @@
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.IO.Ports;
using InverterMon.Shared.Models;
namespace InverterMon.Server.InverterService;
// sealed class StatusData
// {
// public int WorkingMode { get; set; }
// public int BatteryChargingStage { get; set; }
// public double BatteryVoltage { get; set; }
// public int BatteryCurrent { get; set; }
// public int BatteryPower { get; set; }
// public double AcOutputVoltage { get; set; }
// public int AcOutputActivePower { get; set; }
// public int LoadPercentage { get; set; }
// public double PvInputVoltage { get; set; }
// public int PvInputPower { get; set; }
// }
sealed class SettingsData
{
public double BatteryCutOffVoltage { get; set; }
public double BatteryCvChargingVoltage { get; set; }
public double BatteryFloatingChargingVoltage { get; set; }
public double BatteryBackToChargeVoltage { get; set; }
public double BatteryBackToDischargeVoltage { get; set; }
public byte OutputSourcePriority { get; set; }
public byte ChargingSourcePriority { get; set; }
public byte MaxChargingCurrent { get; set; }
public byte MaxAcChargingCurrent { get; set; }
}
[SuppressMessage("Performance", "CA1822:Mark members as static")]
public sealed class FelicitySolarInverter
{
public InverterStatus Status { get; private set; }
const byte SlaveAddress = 0x01;
static SerialPort _serialPort = null!;
internal void Connect(string portName)
{
lock (_lock)
{
_serialPort = new(portName, 2400, Parity.None, 8, StopBits.One);
_serialPort.Open();
}
}
static byte[]? _cachedStatusFrame;
// The status registers we need are located between 0x1101 and 0x112A.
// Total registers to read = (0x112A - 0x1101 + 1)
const ushort StatusStartAddress = 0x1101;
const ushort StatusRegisterCount = 0x112A - 0x1101 + 1; // 42 registers
internal void UpdateStatus()
{
var regs = ReadRegisters(StatusStartAddress, StatusRegisterCount);
// var status = new StatusData
// {
// WorkingMode = regs[0], // 0x1101: Working mode (offset 0)
// 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
}
// The settings registers we need are located between 0x211F and 0x2159.
// Total registers to read = (0x2159 - 0x211F + 1)
const ushort SettingsStartAddress = 0x211F;
const ushort SettingsRegisterCount = 0x2159 - 0x211F + 1; // 59 registers
internal SettingsData ReadSettings()
{
var regs = ReadRegisters(SettingsStartAddress, SettingsRegisterCount);
var settings = new SettingsData
{
BatteryCutOffVoltage = regs[0] / 10.0, // 0x211F: Battery cut-off voltage (offset 0)
BatteryCvChargingVoltage = regs[3] / 10.0, // 0x2122: Battery C.V charging voltage (offset = 0x2122 - 0x211F = 3)
BatteryFloatingChargingVoltage = regs[4] / 10.0, // 0x2123: Battery floating charging voltage (offset = 4)
OutputSourcePriority = (byte)regs[11], // 0x212A: Output source priority (offset = 0x212A - 0x211F = 11)
ChargingSourcePriority = (byte)regs[13], // 0x212C: Charging source priority (offset = 0x212C - 0x211F = 13)
MaxChargingCurrent = (byte)regs[15], // 0x212E: Max charging current (offset = 15)
MaxAcChargingCurrent = (byte)regs[17], // 0x2130: Max AC charging current (offset = 17)
BatteryBackToChargeVoltage = regs[55] / 10.0, // 0x2156: Battery back to charge voltage (offset = 0x2156 - 0x211F = 55)
BatteryBackToDischargeVoltage = regs[58] / 10.0 // 0x2159: Battery back to discharge voltage (offset = 0x2159 - 0x211F = 58)
};
return settings;
}
internal void SetSetting(Setting setting, float value)
{
ushort registerAddress;
switch (setting)
{
case Setting.DischargeCutOff:
registerAddress = 0x211F;
value *= 10; // scale volts to register value
break;
case Setting.BulkVoltage:
registerAddress = 0x2122;
value *= 10;
break;
case Setting.FloatVoltage:
registerAddress = 0x2123;
value *= 10;
break;
case Setting.BackToGrid:
registerAddress = 0x2156;
value *= 10;
break;
case Setting.BackToBattery:
registerAddress = 0x2159;
value *= 10;
break;
case Setting.OutputPriority:
registerAddress = 0x212A; // No scaling needed for priority values (0,1,2 etc.)
break;
case Setting.ChargePriority:
registerAddress = 0x212C;
break;
case Setting.CombinedChargeCurrent:
registerAddress = 0x212E; // Value in amperes (1A per unit)
break;
case Setting.UtilityChargeCurrent:
registerAddress = 0x2130;
break;
default:
throw new ArgumentException("Invalid setting!");
}
WriteSingleRegister(registerAddress, (ushort)value);
}
internal void Close()
{
lock (_lock)
{
if (_serialPort.IsOpen)
_serialPort.Close();
}
}
static short[] ReadRegisters(ushort startAddress, ushort numberOfPoints)
{
// Build Modbus request frame:
// [Slave Address][Function Code 0x03][Start Address Hi][Start Address Lo][Quantity Hi][Quantity Lo][CRC Lo][CRC Hi]
byte[] frame;
var statusRequest = startAddress == StatusStartAddress && numberOfPoints == StatusRegisterCount;
if (statusRequest && _cachedStatusFrame is not null)
frame = _cachedStatusFrame;
else
{
frame = new byte[8];
frame[0] = SlaveAddress;
frame[1] = 0x03;
frame[2] = (byte)(startAddress >> 8);
frame[3] = (byte)(startAddress & 0xFF);
frame[4] = (byte)(numberOfPoints >> 8);
frame[5] = (byte)(numberOfPoints & 0xFF);
var crc = CalculateCrc(frame, 6);
frame[6] = (byte)(crc & 0xFF);
frame[7] = (byte)(crc >> 8);
if (statusRequest)
_cachedStatusFrame = frame;
}
var response = SendModbusRequest(frame);
// Expected response structure:
// [Slave Address][Function Code][Byte Count][Data...][CRC Lo][CRC Hi]
int byteCount = response[2];
var expectedDataBytes = numberOfPoints * 2;
if (byteCount != expectedDataBytes)
throw new InvalidDataException("Unexpected byte count in response!");
var registers = new short[numberOfPoints];
for (var i = 0; i < numberOfPoints; i++)
registers[i] = (short)((response[3 + i * 2] << 8) | response[3 + i * 2 + 1]);
return registers;
}
static void WriteSingleRegister(ushort registerAddress, ushort value)
{
// Build request frame:
// [Slave Address][Function Code 0x06][Register Address Hi][Register Address Lo]
// [Value Hi][Value Lo][CRC Lo][CRC Hi]
var frame = new byte[8];
frame[0] = SlaveAddress;
frame[1] = 0x06;
frame[2] = (byte)(registerAddress >> 8);
frame[3] = (byte)(registerAddress & 0xFF);
frame[4] = (byte)(value >> 8);
frame[5] = (byte)(value & 0xFF);
var crc = CalculateCrc(frame, 6);
frame[6] = (byte)(crc & 0xFF);
frame[7] = (byte)(crc >> 8);
SendModbusRequest(frame);
}
static readonly object _lock = new();
static byte[] SendModbusRequest(byte[] request)
{
lock (_lock) //prevent concurrent access
{
_serialPort.DiscardInBuffer();
_serialPort.DiscardOutBuffer();
var oldTimeout = _serialPort.ReadTimeout;
var buffer = ArrayPool<byte>.Shared.Rent(256);
try
{
_serialPort.ReadTimeout = 1000;
_serialPort.Write(request, 0, request.Length);
var totalBytesRead = ReadBytes(buffer, 0, 3); // Read fixed header (3 bytes)
if ((buffer[1] & 0x80) != 0) // Handle Modbus exceptions (function code with high bit set)
totalBytesRead += ReadBytes(buffer, 3, 2); // Error response: read remaining 2 bytes (CRC)
else
{
// Calculate remaining bytes based on function code
var bytesToRead = GetRemainingBytes(buffer[1], buffer[2]);
totalBytesRead += ReadBytes(buffer, 3, bytesToRead);
}
var response = new byte[totalBytesRead];
Buffer.BlockCopy(buffer, 0, response, 0, totalBytesRead);
return response;
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
_serialPort.ReadTimeout = oldTimeout;
}
}
static int ReadBytes(byte[] b, int offset, int count)
{
var bytesRead = 0;
while (bytesRead < count)
{
var read = _serialPort.Read(b, offset + bytesRead, count - bytesRead);
if (read == 0)
throw new TimeoutException("No data received");
bytesRead += read;
}
return bytesRead;
}
static int GetRemainingBytes(byte functionCode, byte byteCount)
{
return functionCode switch
{
0x03 => 2 + byteCount, // Read holding registers
0x06 => 4, // Write single register (fixed 4 bytes)
0x10 => 4, // Write multiple registers (fixed 4 bytes)
_ => throw new NotSupportedException($"Function code 0x{functionCode:X2} not supported")
};
}
}
static ushort CalculateCrc(byte[] data, int length)
{
ushort crc = 0xFFFF;
for (var pos = 0; pos < length; pos++)
{
crc ^= data[pos];
for (var i = 0; i < 8; i++)
{
if ((crc & 0x0001) != 0)
{
crc >>= 1;
crc ^= 0xA001;
}
else
crc >>= 1;
}
}
return crc;
}
}

View File

@ -3,18 +3,18 @@ using InverterMon.Server.Persistence.Settings;
namespace InverterMon.Server.InverterService; namespace InverterMon.Server.InverterService;
class StatusRetriever(Database db, CurrentStatus currentStatus, UserSettings userSettings) : BackgroundService class StatusRetriever(Database db, FelicitySolarInverter inverter, UserSettings userSettings) : BackgroundService
{ {
protected override async Task ExecuteAsync(CancellationToken c) protected override async Task ExecuteAsync(CancellationToken c)
{ {
while (!c.IsCancellationRequested) while (!c.IsCancellationRequested)
{ {
currentStatus.Result.BatteryCapacity = userSettings.BatteryCapacity; inverter.Status.BatteryCapacity = userSettings.BatteryCapacity;
currentStatus.Result.PV_MaxCapacity = userSettings.PV_MaxCapacity; inverter.Status.PV_MaxCapacity = userSettings.PV_MaxCapacity;
//todo: get data from inverter and map to CurrentStatus.Result //todo: get data from inverter and map to CurrentStatus.Result
_ = db.UpdateTodaysPvGeneration(currentStatus, c); db.UpdateTodaysPvGeneration(c);
await Task.Delay(2000); await Task.Delay(2000);
} }

View File

@ -8,15 +8,15 @@ namespace InverterMon.Server.Persistence;
public class Database public class Database
{ {
readonly LiteDatabase _db; readonly LiteDatabase _db;
readonly CurrentStatus _currentStatus; readonly FelicitySolarInverter _inverter;
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, CurrentStatus status, UserSettings settings) public Database(IHostApplicationLifetime lifetime, FelicitySolarInverter inverter, UserSettings settings)
{ {
_currentStatus = status; _inverter = inverter;
_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());
@ -36,18 +36,18 @@ public class Database
.SingleOrDefault(); .SingleOrDefault();
if (_today is not null) if (_today is not null)
_currentStatus.Result.RestorePVWattHours(_today.TotalWattHours); _inverter.Status.RestorePVWattHours(_today.TotalWattHours);
else else
{ {
_today = new() { Id = todayDayNumber }; _today = new() { Id = todayDayNumber };
_today.SetTotalWattHours(0); _today.SetTotalWattHours(0);
_currentStatus.Result.RestorePVWattHours(0); _inverter.Status.RestorePVWattHours(0);
_pvGenCollection.Insert(_today); _pvGenCollection.Insert(_today);
_db.Checkpoint(); _db.Checkpoint();
} }
} }
public async Task UpdateTodaysPvGeneration(CurrentStatus cmd, CancellationToken c) public void UpdateTodaysPvGeneration(CancellationToken c)
{ {
var hourNow = DateTime.Now.Hour; var hourNow = DateTime.Now.Hour;
@ -58,13 +58,13 @@ public class Database
if (_today?.Id == todayDayNumber) if (_today?.Id == todayDayNumber)
{ {
_today.SetWattPeaks(cmd.Result.PVInputWatt); _today.SetWattPeaks(_inverter.Status.PVInputWatt);
_today.SetTotalWattHours(cmd.Result.PVInputWattHour); _today.SetTotalWattHours(_inverter.Status.PVInputWattHour);
_pvGenCollection.Update(_today); _pvGenCollection.Update(_today);
} }
else else
{ {
cmd.Result.ResetPVWattHourAccumulation(); //it's a new day. start accumulation from scratch. _inverter.Status.ResetPVWattHourAccumulation(); //it's a new day. start accumulation from scratch.
_today = new() { Id = todayDayNumber }; _today = new() { Id = todayDayNumber };
_today.SetTotalWattHours(0); _today.SetTotalWattHours(0);
_pvGenCollection.Insert(_today); _pvGenCollection.Insert(_today);

View File

@ -4,7 +4,7 @@ public class PVGeneration
{ {
public int Id { get; set; } public int Id { get; set; }
public Dictionary<string, int> WattPeaks { get; set; } = new(); public Dictionary<string, int> WattPeaks { get; set; } = new();
public decimal TotalWattHours { get; set; } public double TotalWattHours { get; set; }
public void SetWattPeaks(int newValue) public void SetWattPeaks(int newValue)
{ {
@ -19,7 +19,7 @@ public class PVGeneration
WattPeaks[key] = newValue; WattPeaks[key] = newValue;
} }
public void SetTotalWattHours(decimal totalWattHours) public void SetTotalWattHours(double totalWattHours)
{ {
TotalWattHours = totalWattHours; TotalWattHours = totalWattHours;
} }

View File

@ -18,7 +18,6 @@ _ = 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<Database>() .AddSingleton<Database>()
.AddSingleton<JkBms>(); .AddSingleton<JkBms>();

View File

@ -1,8 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -2,9 +2,7 @@
"LaunchSettings": { "LaunchSettings": {
"DeviceAddress": "/dev/ttyUSB1", "DeviceAddress": "/dev/ttyUSB1",
"JkBmsAddress": "/dev/ttyUSB0", "JkBmsAddress": "/dev/ttyUSB0",
"WebPort": 80, "WebPort": 80
"TroubleMode": "no",
"MppSolarPath": "/usr/local/bin/mpp-solar"
}, },
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {

View File

@ -8,16 +8,16 @@ public class InverterStatus
public int BatteryCapacity { get; set; } = 100; public int BatteryCapacity { get; set; } = 100;
[JsonPropertyName("b")] [JsonPropertyName("b")]
public decimal BatteryChargeCRate => BatteryChargeCurrent == 0 ? 0 : Convert.ToDecimal(BatteryChargeCurrent) / BatteryCapacity; public double BatteryChargeCRate => BatteryChargeCurrent == 0 ? 0 : Convert.ToDouble(BatteryChargeCurrent) / BatteryCapacity;
[JsonPropertyName("c")] [JsonPropertyName("c")]
public int BatteryChargeCurrent { get; set; } public int BatteryChargeCurrent { get; set; }
[JsonPropertyName("d")] [JsonPropertyName("d")]
public int BatteryChargeWatts => BatteryChargeCurrent == 0 ? 0 : Convert.ToInt32(BatteryChargeCurrent * BatteryVoltage); public int BatteryChargeWatts { get; set; }
[JsonPropertyName("e")] [JsonPropertyName("e")]
public decimal BatteryDischargeCRate => BatteryDischargeCurrent == 0 ? 0 : Convert.ToDecimal(BatteryDischargeCurrent) / BatteryCapacity; public double BatteryDischargeCRate => BatteryDischargeCurrent == 0 ? 0 : Convert.ToDouble(BatteryDischargeCurrent) / BatteryCapacity;
[JsonPropertyName("f")] [JsonPropertyName("f")]
public int BatteryDischargeCurrent { get; set; } public int BatteryDischargeCurrent { get; set; }
@ -26,37 +26,37 @@ public class InverterStatus
public int BatteryDischargePotential => BatteryDischargeCurrent > 0 ? Convert.ToInt32(Convert.ToDouble(BatteryDischargeCurrent) / BatteryCapacity * 100) : 0; public int BatteryDischargePotential => BatteryDischargeCurrent > 0 ? Convert.ToInt32(Convert.ToDouble(BatteryDischargeCurrent) / BatteryCapacity * 100) : 0;
[JsonPropertyName("h")] [JsonPropertyName("h")]
public int BatteryDischargeWatts => BatteryDischargeCurrent == 0 ? 0 : Convert.ToInt32(BatteryDischargeCurrent * BatteryVoltage); public int BatteryDischargeWatts { get; set; }
[JsonPropertyName("i")] [JsonPropertyName("i")]
public decimal BatteryVoltage { get; set; } public double BatteryVoltage { get; set; }
[JsonPropertyName("j")] [JsonPropertyName("j")]
public int GridUsageWatts => GridVoltage < 10 ? 0 : LoadWatts + BatteryChargeWatts - (PVInputWatt + BatteryDischargeWatts); public int GridUsageWatts => GridVoltage < 10 ? 0 : LoadWatts + BatteryChargeWatts - (PVInputWatt + BatteryDischargeWatts);
[JsonPropertyName("k")] [JsonPropertyName("k")]
public decimal GridVoltage { get; set; } public double GridVoltage { get; set; }
[JsonPropertyName("l")] [JsonPropertyName("l")]
public int HeatSinkTemperature { get; set; } public int HeatSinkTemperature { get; set; }
[JsonPropertyName("m")] [JsonPropertyName("m")]
public decimal LoadCurrent => LoadWatts == 0 ? 0 : LoadWatts / OutputVoltage; public double LoadCurrent => LoadWatts == 0 ? 0 : LoadWatts / OutputVoltage;
[JsonPropertyName("n")] [JsonPropertyName("n")]
public decimal LoadPercentage { get; set; } public int LoadPercentage { get; set; }
[JsonPropertyName("o")] [JsonPropertyName("o")]
public int LoadWatts { get; set; } public int LoadWatts { get; set; }
[JsonPropertyName("p")] [JsonPropertyName("p")]
public decimal OutputVoltage { get; set; } public double OutputVoltage { get; set; }
[JsonPropertyName("q")] [JsonPropertyName("q")]
public decimal PVInputCurrent { get; set; } public double PVInputCurrent { get; set; }
[JsonPropertyName("r")] [JsonPropertyName("r")]
public decimal PVInputVoltage { get; set; } public double PVInputVoltage { get; set; }
[JsonPropertyName("s")] [JsonPropertyName("s")]
public int PVInputWatt public int PVInputWatt
@ -69,13 +69,13 @@ public class InverterStatus
pvInputWatt = value; pvInputWatt = value;
var interval = (DateTime.Now - pvInputWattHourLastComputed).TotalSeconds; var interval = (DateTime.Now - pvInputWattHourLastComputed).TotalSeconds;
PVInputWattHour += value / (3600 / Convert.ToDecimal(interval)); PVInputWattHour += value / (3600 / Convert.ToDouble(interval));
pvInputWattHourLastComputed = DateTime.Now; pvInputWattHourLastComputed = DateTime.Now;
} }
} }
[JsonPropertyName("t")] [JsonPropertyName("t")]
public decimal PVInputWattHour { get; private set; } public double PVInputWattHour { get; private set; }
[JsonPropertyName("u")] [JsonPropertyName("u")]
public int PV_MaxCapacity { get; set; } public int PV_MaxCapacity { get; set; }
@ -86,7 +86,7 @@ public class InverterStatus
int pvInputWatt; int pvInputWatt;
DateTime pvInputWattHourLastComputed; DateTime pvInputWattHourLastComputed;
public void RestorePVWattHours(decimal accruedWattHours) public void RestorePVWattHours(double accruedWattHours)
{ {
PVInputWattHour = accruedWattHours; PVInputWattHour = accruedWattHours;
pvInputWattHourLastComputed = DateTime.Now; pvInputWattHourLastComputed = DateTime.Now;

View File

@ -6,7 +6,7 @@ public class PVDay
{ {
public int DayNumber { get; set; } public int DayNumber { get; set; }
public string DayName { get; set; } public string DayName { get; set; }
public decimal TotalKiloWattHours { get; set; } public double TotalKiloWattHours { get; set; }
public IEnumerable<WattPeak> WattPeaks { get; set; } public IEnumerable<WattPeak> WattPeaks { get; set; }
public int GraphTickCount { get; set; } public int GraphTickCount { get; set; }
public int[] GraphRange { get; set; } public int[] GraphRange { get; set; }