This commit is contained in:
Sina Blattmann 2023-03-06 08:47:34 +01:00
commit 8dd591d752
57 changed files with 909 additions and 565 deletions

View File

@ -6,9 +6,6 @@
<ProjectReference Include="../../Lib/Victron/VictronVRM/VictronVRM.csproj" /> <ProjectReference Include="../../Lib/Victron/VictronVRM/VictronVRM.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Remove="RemoteSupportConsole.csproj.DotSettings" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CliWrap" Version="3.6.0" /> <PackageReference Include="CliWrap" Version="3.6.0" />

View File

@ -6,6 +6,6 @@ public static class Utils
{ {
public static Decimal Round3(this Decimal d) public static Decimal Round3(this Decimal d)
{ {
return DecimalUtils.RoundToSignificantFigures(d, 3); return DecimalUtils.RoundToSignificantDigits(d, 3);
} }
} }

View File

@ -1,5 +1,5 @@
using InnovEnergy.Lib.StatusApi;
using InnovEnergy.Lib.StatusApi.Connections; using InnovEnergy.Lib.StatusApi.Connections;
using InnovEnergy.Lib.StatusApi.Devices;
namespace InnovEnergy.Lib.Devices.AMPT; namespace InnovEnergy.Lib.Devices.AMPT;
@ -11,5 +11,5 @@ public record AmptDeviceStatus
UInt32 Timestamp, // The UTC timestamp of the measurements UInt32 Timestamp, // The UTC timestamp of the measurements
Decimal ProductionToday, // converted to kW in AmptCU class Decimal ProductionToday, // converted to kW in AmptCU class
IReadOnlyList<DcConnection> Strings IReadOnlyList<DcConnection> Strings
): Mppt(Dc, Strings) ): MpptStatus(Dc, Strings)
{} {}

View File

@ -1,7 +1,5 @@
using InnovEnergy.Lib.Protocols.Modbus.Clients; using InnovEnergy.Lib.Protocols.Modbus.Clients;
using InnovEnergy.Lib.Protocols.Modbus.Connections; using InnovEnergy.Lib.Protocols.Modbus.Connections;
using InnovEnergy.Lib.Protocols.Modbus.Conversions;
using InnovEnergy.Lib.StatusApi.Connections;
namespace InnovEnergy.Lib.Devices.Battery48TL; namespace InnovEnergy.Lib.Devices.Battery48TL;
@ -33,18 +31,11 @@ public class Battery48TlDevice
public Battery48TLStatus? ReadStatus() //Already try catch is implemented public Battery48TLStatus? ReadStatus() //Already try catch is implemented
{ {
if (Modbus is null) // TODO : remove fake
{
Console.WriteLine("Battery is null");
return null;
}
// Console.WriteLine("Reading Battery Data");
try try
{ {
var registers = Modbus.ReadInputRegisters(Constants.BaseAddress, Constants.NoOfRegisters); return Modbus
return TryReadStatus(registers); .ReadInputRegisters(Constants.BaseAddress, Constants.NoOfRegisters)
.ParseBatteryStatus();
} }
catch (Exception e) catch (Exception e)
{ {
@ -53,92 +44,4 @@ public class Battery48TlDevice
return null; return null;
} }
} }
private Battery48TLStatus? TryReadStatus(ModbusRegisters data)
{
var soc = data.ParseDecimal(register: 1054, scaleFactor: 0.1m);
var eocReached = data.ParseEocReached();
var warnings = new List<String>();
if (data.ParseBool(1006, 1)) warnings.Add("TaM1: BMS temperature high");
if (data.ParseBool(1006, 4)) warnings.Add("TbM1: Battery temperature high");
if (data.ParseBool(1006, 6)) warnings.Add("VBm1: Bus voltage low");
if (data.ParseBool(1006, 8)) warnings.Add("VBM1: Bus voltage high");
if (data.ParseBool(1006, 10)) warnings.Add("IDM1: Discharge current high");
if (data.ParseBool(1006, 24)) warnings.Add("vsM1: String voltage high");
if (data.ParseBool(1006, 26)) warnings.Add("iCM1: Charge current high");
if (data.ParseBool(1006, 28)) warnings.Add("iDM1: Discharge current high");
if (data.ParseBool(1006, 30)) warnings.Add("MID1: String voltages unbalanced");
if (data.ParseBool(1006, 32)) warnings.Add("BLPW: Not enough charging power on bus");
if (data.ParseBool(1006, 35)) warnings.Add("Ah_W: String SOC low");
if (data.ParseBool(1006, 38)) warnings.Add("MPMM: Midpoint wiring problem");
if (data.ParseBool(1006, 39)) warnings.Add("TCMM:");
if (data.ParseBool(1006, 40)) warnings.Add("TCdi: Temperature difference between strings high");
if (data.ParseBool(1006, 41)) warnings.Add("WMTO:");
if (data.ParseBool(1006, 44)) warnings.Add("bit44:");
if (data.ParseBool(1006, 46)) warnings.Add("CELL1:");
var alarms = new List<String>();
if (data.ParseBool(1010, 0)) alarms.Add("Tam : BMS temperature too low");
if (data.ParseBool(1010, 2)) alarms.Add("TaM2 : BMS temperature too high");
if (data.ParseBool(1010, 3)) alarms.Add("Tbm : Battery temperature too low");
if (data.ParseBool(1010, 5)) alarms.Add("TbM2 : Battery temperature too high");
if (data.ParseBool(1010, 7)) alarms.Add("VBm2 : Bus voltage too low");
if (data.ParseBool(1010, 9)) alarms.Add("VBM2 : Bus voltage too high");
if (data.ParseBool(1010, 11)) alarms.Add("IDM2 : Discharge current too high");
if (data.ParseBool(1010, 12)) alarms.Add("ISOB : Electrical insulation failure");
if (data.ParseBool(1010, 13)) alarms.Add("MSWE : Main switch failure");
if (data.ParseBool(1010, 14)) alarms.Add("FUSE : Main fuse blown");
if (data.ParseBool(1010, 15)) alarms.Add("HTRE : Battery failed to warm up");
if (data.ParseBool(1010, 16)) alarms.Add("TCPE : Temperature sensor failure");
if (data.ParseBool(1010, 17)) alarms.Add("STRE :");
if (data.ParseBool(1010, 18)) alarms.Add("CME : Current sensor failure");
if (data.ParseBool(1010, 19)) alarms.Add("HWFL : BMS hardware failure");
if (data.ParseBool(1010, 20)) alarms.Add("HWEM : Hardware protection tripped");
if (data.ParseBool(1010, 21)) alarms.Add("ThM : Heatsink temperature too high");
if (data.ParseBool(1010, 22)) alarms.Add("vsm1 : String voltage too low");
if (data.ParseBool(1010, 23)) alarms.Add("vsm2 : Low string voltage failure");
if (data.ParseBool(1010, 25)) alarms.Add("vsM2 : String voltage too high");
if (data.ParseBool(1010, 27)) alarms.Add("iCM2 : Charge current too high");
if (data.ParseBool(1010, 29)) alarms.Add("iDM2 : Discharge current too high");
if (data.ParseBool(1010, 31)) alarms.Add("MID2 : String voltage unbalance too high");
if (data.ParseBool(1010, 33)) alarms.Add("CCBF : Internal charger hardware failure");
if (data.ParseBool(1010, 34)) alarms.Add("AhFL :");
if (data.ParseBool(1010, 36)) alarms.Add("TbCM :");
if (data.ParseBool(1010, 37)) alarms.Add("BRNF :");
if (data.ParseBool(1010, 42)) alarms.Add("HTFS : If Heaters Fuse Blown");
if (data.ParseBool(1010, 43)) alarms.Add("DATA : Parameters out of range");
if (data.ParseBool(1010, 45)) alarms.Add("CELL2:");
return new Battery48TLStatus(
Dc: new DcConnection
(
Voltage : data.ReadVoltage(),
Current : data.ReadCurrent()),
Soc : !eocReached && soc >= 100m ? 99.9m : soc,
Temperature : data.ParseDecimal(register: 1004, scaleFactor: 0.1m, offset: -400),
BusVoltage : data.ParseDecimal(register: 1002, scaleFactor: 0.01m),
GreenLed : data.ParseLedState(register: 1005, led: LedColor.Green),
AmberLed : data.ParseLedState(register: 1006, led: LedColor.Amber),
BlueLed : data.ParseLedState(register: 1005, led: LedColor.Blue),
RedLed : data.ParseLedState(register: 1005, led: LedColor.Red),
Warnings : warnings,
Alarms : alarms,
MainSwitchClosed : data.ParseBool(baseRegister: 1014, bit: 0),
AlarmOutActive : data.ParseBool(baseRegister: 1014, bit: 1),
InternalFanActive : data.ParseBool(baseRegister: 1014, bit: 2),
VoltMeasurementAllowed: data.ParseBool(baseRegister: 1014, bit: 3),
AuxRelay : data.ParseBool(baseRegister: 1014, bit: 4),
RemoteState : data.ParseBool(baseRegister: 1014, bit: 5),
HeaterOn : data.ParseBool(baseRegister: 1014, bit: 6),
EocReached : eocReached,
BatteryCold : data.ParseBatteryCold(),
MaxChargingPower : data.CalcMaxChargePower(),
MaxDischargingPower : data.CalcMaxDischargePower()
);
}
} }

View File

@ -1,39 +1,50 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using InnovEnergy.Lib.Protocols.Modbus.Conversions; using InnovEnergy.Lib.StatusApi;
using InnovEnergy.Lib.StatusApi.Connections; using InnovEnergy.Lib.Units;
using InnovEnergy.Lib.StatusApi.Devices; using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Devices.Battery48TL; namespace InnovEnergy.Lib.Devices.Battery48TL;
using T = Battery48TLStatus;
[SuppressMessage("ReSharper", "InconsistentNaming")] [SuppressMessage("ReSharper", "InconsistentNaming")]
public record Battery48TLStatus public record Battery48TLStatus : BatteryStatus
(
DcConnection Dc,
Decimal Soc,
Decimal Temperature,
//Decimal Current,
//Decimal Voltage,
Decimal BusVoltage,
LedState GreenLed,
LedState AmberLed,
LedState BlueLed,
LedState RedLed,
IReadOnlyList<String> Warnings,
IReadOnlyList<String> Alarms,
Boolean MainSwitchClosed,
Boolean AlarmOutActive,
Boolean InternalFanActive,
Boolean VoltMeasurementAllowed,
Boolean AuxRelay,
Boolean RemoteState,
Boolean HeaterOn,
Boolean EocReached,
Boolean BatteryCold,
Decimal MaxChargingPower,
Decimal MaxDischargingPower
)
: Battery(Dc, Soc, Temperature)
{ {
public Voltage CellsVoltage { get; init; }
public Power MaxChargingPower { get; init; }
public Power MaxDischargingPower { get; init; }
public State GreenLed { get; init; }
public State AmberLed { get; init; }
public State BlueLed { get; init; }
public State RedLed { get; init; }
public State Warnings { get; init; }
public State Alarms { get; init; }
public State MainSwitchState { get; init; } // connected to bus | disconnected from bus
public State HeaterState { get; init; } // heating | not heating
public State EocState { get; init; } // EOC reached | EOC not reached
public State TemperatureState { get; init; } // cold | operating temperature | overheated
public static T operator |(T left, T right) => OpParallel(left, right);
private static readonly Func<T, T, T> OpParallel = "|".CreateBinaryOpForProps<T>();
// TODO: strings
// TODO
// public State LimitedBy { get; init; }
// TODO
// public Boolean AlarmOutActive { get; init; }
// public Boolean InternalFanActive { get; init; }
// public Boolean VoltMeasurementAllowed { get; init; }
// public Boolean AuxRelay { get; init; }
// public Boolean RemoteState { get; init; }
} }

View File

@ -1,167 +0,0 @@
using InnovEnergy.Lib.Protocols.Modbus.Conversions;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Devices.Battery48TL;
public static class BatteryDataParser
{
public static Decimal ParseDecimal(this ModbusRegisters data, Int32 register, Decimal scaleFactor = 1.0m, Double offset = 0.0)
{
var value = data[register].ConvertTo<Int32>(); // widen to 32bit signed
if (value >= 0x8000)
value -= 0x10000; // Fiamm stores their integers signed AND with sign-offset @#%^&!
return (Decimal)(value + offset) * scaleFactor;
}
internal static Decimal ReadCurrent(this ModbusRegisters data)
{
return ParseDecimal(data, register: 1001, scaleFactor: 0.01m, offset: -10000);
}
internal static Decimal ReadVoltage(this ModbusRegisters data)
{
return ParseDecimal(data, register: 1000, scaleFactor: 0.01m);
}
internal static Boolean ParseBool(this ModbusRegisters data, Int32 baseRegister, Int16 bit)
{
var x = bit / 16;
var y = bit % 16;
var value = (UInt32)data[baseRegister + x];
return (value & (1 << y)) > 0;
}
internal static LedState ParseLedState(this ModbusRegisters data, Int32 register, LedColor led)
{
var lo = ParseBool(data, register, (led.ConvertTo<Int16>() * 2).ConvertTo<Int16>());
var hi = ParseBool(data, register, (led.ConvertTo<Int16>() * 2 + 1).ConvertTo<Int16>());
if (hi)
{
if (lo)
{
return LedState.BlinkingFast;
}
else
{
return LedState.BlinkingSlow;
}
}
else
{
if (lo)
{
return LedState.On;
}
else
{
return LedState.Off;
}
}
}
internal static String ParseRegisters(this ModbusRegisters data, Int32 register, Int16 count)
{
var container = "";
var start = register;
var end = register + count;
for (var i = start; i < end; i++)
{
var binary = Convert.ToString(data[register], 2);
container += binary.PadLeft(16, '0');
}
return container;
}
internal static Boolean ParseEocReached(this ModbusRegisters data)
{
return ParseLedState(data, 1005, LedColor.Green) == LedState.On &&
ParseLedState(data, 1005, LedColor.Amber) == LedState.Off &&
ParseLedState(data, 1005, LedColor.Blue) == LedState.Off;
}
internal static Boolean ParseBatteryCold(this ModbusRegisters data)
{
return ParseLedState(data, 1005, LedColor.Green) >= LedState.BlinkingSlow &&
ParseLedState(data, 1005, LedColor.Blue) >= LedState.BlinkingSlow;
}
private static Decimal CalcPowerLimitImposedByVoltageLimit(Decimal v,Decimal i,Decimal vLimit,Decimal rInt)
{
var dv = vLimit - v;
var di = dv / rInt;
var pLimit = vLimit * (i + di);
return pLimit;
}
private static Decimal CalcPowerLimitImposedByCurrentLimit(Decimal v, Decimal i, Decimal iLimit, Decimal rInt)
{
var di = iLimit - i;
var dv = di * rInt;
var pLimit = iLimit * (v + dv);
return pLimit;
}
private static Decimal CalcPowerLimitImposedByTempLimit(Decimal t, Decimal maxAllowedTemp, Decimal power , Decimal setpoint)
{
// const Int32 holdZone = 300;
// const Int32 maxAllowedTemp = 315;
var kp = 0.05m;
var error = setpoint - power;
var controlOutput = (kp * error) *(1 - Math.Abs((t-307.5m)/7.5m));
return controlOutput;
// var a = holdZone - maxAllowedTemp;
// var b = -a * maxAllowedTemp;
}
internal static Decimal CalcMaxChargePower(this ModbusRegisters data)
{
var v = ReadVoltage(data);
var i = ReadCurrent(data);
var pLimits = new[]
{
CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMax, Constants.RIntMin),
CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMax, Constants.RIntMax),
CalcPowerLimitImposedByCurrentLimit(v, i, Constants.IMax, Constants.RIntMin),
CalcPowerLimitImposedByCurrentLimit(v, i, Constants.IMax, Constants.RIntMax)
};
var pLimit = pLimits.Min();
return Math.Max(pLimit, 0);
}
internal static Decimal CalcMaxDischargePower(this ModbusRegisters data)
{
var v = ReadVoltage(data);
var i = ReadCurrent(data);
var t = data.ParseDecimal(register: 1004, scaleFactor: 0.1m, offset: -400);
var pLimits = new[]
{
CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMin, Constants.RIntMin),
CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMin, Constants.RIntMax),
CalcPowerLimitImposedByCurrentLimit(v, i, -Constants.IMax, Constants.RIntMin),
CalcPowerLimitImposedByCurrentLimit(v, i, -Constants.IMax, Constants.RIntMax),
// CalcPowerLimitImposedByTempLimit(t,315,300)
};
var pLimit = pLimits.Max();
return Math.Min(pLimit, 0);
}
}

View File

@ -0,0 +1,272 @@
using System.Diagnostics.CodeAnalysis;
using InnovEnergy.Lib.Protocols.Modbus.Conversions;
using InnovEnergy.Lib.Units;
using InnovEnergy.Lib.Units.Composite;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Devices.Battery48TL;
public static class ModbusParser
{
internal static Battery48TLStatus ParseBatteryStatus(this ModbusRegisters data)
{
return new Battery48TLStatus
{
Dc = data.ParseDcBus(),
Alarms = data.ParseAlarms().ToList(),
Warnings = data.ParseWarnings().ToList(),
Soc = data.ParseSoc(),
Temperature = data.ParseTemperature(),
GreenLed = data.ParseGreenLed(),
AmberLed = data.ParseAmberLed(),
BlueLed = data.ParseBlueLed(),
RedLed = data.ParseRedLed(),
MainSwitchState = data.ParseMainSwitchState(),
HeaterState = data.ParseHeaterState(),
EocState = data.ParseEocState(),
TemperatureState = data.ParseTemperatureState(),
MaxChargingPower = data.CalcMaxChargePower(),
MaxDischargingPower = data.CalcMaxDischargePower(),
CellsVoltage = data.ParseCellsVoltage(),
};
}
public static Decimal ParseDecimal(this ModbusRegisters data, Int32 register, Decimal scaleFactor = 1.0m, Double offset = 0.0)
{
var value = data[register].ConvertTo<Int32>(); // widen to 32bit signed
if (value >= 0x8000)
value -= 0x10000; // Fiamm stores their integers signed AND with sign-offset @#%^&!
return (Decimal)(value + offset) * scaleFactor;
}
internal static Decimal ParseCurrent(this ModbusRegisters data)
{
return data.ParseDecimal(register: 1001, scaleFactor: 0.01m, offset: -10000);
}
internal static Decimal ParseCellsVoltage(this ModbusRegisters data)
{
return data.ParseDecimal(register: 1000, scaleFactor: 0.01m);
}
internal static Decimal ParseBusVoltage(this ModbusRegisters data)
{
return data.ParseDecimal(register: 1002, scaleFactor: 0.01m);
}
internal static Boolean ParseBool(this ModbusRegisters data, Int32 baseRegister, Int16 bit)
{
var x = bit / 16;
var y = bit % 16;
var value = (UInt32)data[baseRegister + x];
return (value & (1 << y)) > 0;
}
internal static LedState ParseLedState(this ModbusRegisters data, Int32 register, LedColor led)
{
var lo = data.ParseBool(register, (led.ConvertTo<Int16>() * 2 ).ConvertTo<Int16>());
var hi = data.ParseBool(register, (led.ConvertTo<Int16>() * 2 + 1).ConvertTo<Int16>());
return (hi, lo) switch
{
(false, false) => LedState.Off,
(false, true) => LedState.On,
(true, false) => LedState.BlinkingSlow,
(true, true) => LedState.BlinkingFast,
};
}
private static Boolean ParseEocReached(this ModbusRegisters data)
{
return ParseLedState(data, 1005, LedColor.Green) == LedState.On &&
ParseLedState(data, 1005, LedColor.Amber) == LedState.Off &&
ParseLedState(data, 1005, LedColor.Blue) == LedState.Off;
}
internal static State ParseTemperatureState(this ModbusRegisters data)
{
return data.ParseBatteryCold() ? "cold" : "operating temperature"; // TODO: overheated,
}
internal static Decimal ParseTemperature(this ModbusRegisters data)
{
return data.ParseDecimal(register: 1004, scaleFactor: 0.1m, offset: -400);
}
internal static Decimal ParseSoc(this ModbusRegisters data)
{
return data.ParseDecimal(register: 1054, scaleFactor: 0.1m);
}
internal static State ParseEocState(this ModbusRegisters data)
{
return data.ParseEocReached() ? "EOC reached" : "EOC not reached";
}
internal static State ParseHeaterState(this ModbusRegisters data)
{
return data.ParseBool(baseRegister: 1014, bit: 6) ? "heating" : "not heating";
}
internal static State ParseMainSwitchState(this ModbusRegisters data)
{
return data.ParseBool(baseRegister: 1014, bit: 0) ? "connected to bus" : "disconnected from bus";
}
internal static Boolean ParseBatteryCold(this ModbusRegisters data)
{
return ParseLedState(data, 1005, LedColor.Green) >= LedState.BlinkingSlow &&
ParseLedState(data, 1005, LedColor.Blue) >= LedState.BlinkingSlow;
}
private static Decimal CalcPowerLimitImposedByVoltageLimit(Decimal v,Decimal i,Decimal vLimit,Decimal rInt)
{
var dv = vLimit - v;
var di = dv / rInt;
var pLimit = vLimit * (i + di);
return pLimit;
}
private static Decimal CalcPowerLimitImposedByCurrentLimit(Decimal v, Decimal i, Decimal iLimit, Decimal rInt)
{
var di = iLimit - i;
var dv = di * rInt;
var pLimit = iLimit * (v + dv);
return pLimit;
}
private static Decimal CalcPowerLimitImposedByTempLimit(Decimal t, Decimal maxAllowedTemp, Decimal power , Decimal setpoint)
{
// const Int32 holdZone = 300;
// const Int32 maxAllowedTemp = 315;
var kp = 0.05m;
var error = setpoint - power;
var controlOutput = (kp * error) *(1 - Math.Abs((t-307.5m)/7.5m));
return controlOutput;
// var a = holdZone - maxAllowedTemp;
// var b = -a * maxAllowedTemp;
}
internal static Decimal CalcMaxChargePower(this ModbusRegisters data)
{
var v = ParseCellsVoltage(data);
var i = ParseCurrent(data);
var pLimits = new[]
{
CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMax, Constants.RIntMin),
CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMax, Constants.RIntMax),
CalcPowerLimitImposedByCurrentLimit(v, i, Constants.IMax, Constants.RIntMin),
CalcPowerLimitImposedByCurrentLimit(v, i, Constants.IMax, Constants.RIntMax)
};
var pLimit = pLimits.Min();
return Math.Max(pLimit, 0);
}
internal static DcBus ParseDcBus(this ModbusRegisters data)
{
return new()
{
Current = data.ParseCurrent(),
Voltage = data.ParseBusVoltage(),
};
}
internal static Decimal CalcMaxDischargePower(this ModbusRegisters data)
{
var v = ParseCellsVoltage(data);
var i = ParseCurrent(data);
var t = data.ParseDecimal(register: 1004, scaleFactor: 0.1m, offset: -400);
var pLimits = new[]
{
CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMin, Constants.RIntMin),
CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMin, Constants.RIntMax),
CalcPowerLimitImposedByCurrentLimit(v, i, -Constants.IMax, Constants.RIntMin),
CalcPowerLimitImposedByCurrentLimit(v, i, -Constants.IMax, Constants.RIntMax),
// CalcPowerLimitImposedByTempLimit(t,315,300)
};
var pLimit = pLimits.Max();
return Math.Min(pLimit, 0);
}
internal static LedState ParseGreenLed(this ModbusRegisters data) => data.ParseLedState(register: 1005, led: LedColor.Green);
internal static LedState ParseAmberLed(this ModbusRegisters data) => data.ParseLedState(register: 1006, led: LedColor.Amber);
internal static LedState ParseBlueLed (this ModbusRegisters data) => data.ParseLedState(register: 1005, led: LedColor.Blue);
internal static LedState ParseRedLed (this ModbusRegisters data) => data.ParseLedState(register: 1005, led: LedColor.Red);
[SuppressMessage("ReSharper", "StringLiteralTypo")]
internal static IEnumerable<String> ParseAlarms(this ModbusRegisters data)
{
if (data.ParseBool(1010, 0)) yield return "Tam : BMS temperature too low";
if (data.ParseBool(1010, 2)) yield return "TaM2 : BMS temperature too high";
if (data.ParseBool(1010, 3)) yield return "Tbm : Battery temperature too low";
if (data.ParseBool(1010, 5)) yield return "TbM2 : Battery temperature too high";
if (data.ParseBool(1010, 7)) yield return "VBm2 : Bus voltage too low";
if (data.ParseBool(1010, 9)) yield return "VBM2 : Bus voltage too high";
if (data.ParseBool(1010, 11)) yield return "IDM2 : Discharge current too high";
if (data.ParseBool(1010, 12)) yield return "ISOB : Electrical insulation failure";
if (data.ParseBool(1010, 13)) yield return "MSWE : Main switch failure";
if (data.ParseBool(1010, 14)) yield return "FUSE : Main fuse blown";
if (data.ParseBool(1010, 15)) yield return "HTRE : Battery failed to warm up";
if (data.ParseBool(1010, 16)) yield return "TCPE : Temperature sensor failure";
if (data.ParseBool(1010, 17)) yield return "STRE :";
if (data.ParseBool(1010, 18)) yield return "CME : Current sensor failure";
if (data.ParseBool(1010, 19)) yield return "HWFL : BMS hardware failure";
if (data.ParseBool(1010, 20)) yield return "HWEM : Hardware protection tripped";
if (data.ParseBool(1010, 21)) yield return "ThM : Heatsink temperature too high";
if (data.ParseBool(1010, 22)) yield return "vsm1 : String voltage too low";
if (data.ParseBool(1010, 23)) yield return "vsm2 : Low string voltage failure";
if (data.ParseBool(1010, 25)) yield return "vsM2 : String voltage too high";
if (data.ParseBool(1010, 27)) yield return "iCM2 : Charge current too high";
if (data.ParseBool(1010, 29)) yield return "iDM2 : Discharge current too high";
if (data.ParseBool(1010, 31)) yield return "MID2 : String voltage unbalance too high";
if (data.ParseBool(1010, 33)) yield return "CCBF : Internal charger hardware failure";
if (data.ParseBool(1010, 34)) yield return "AhFL :";
if (data.ParseBool(1010, 36)) yield return "TbCM :";
if (data.ParseBool(1010, 37)) yield return "BRNF :";
if (data.ParseBool(1010, 42)) yield return "HTFS : If Heaters Fuse Blown";
if (data.ParseBool(1010, 43)) yield return "DATA : Parameters out of range";
if (data.ParseBool(1010, 45)) yield return "CELL2:";
}
[SuppressMessage("ReSharper", "StringLiteralTypo")]
internal static IEnumerable<String> ParseWarnings(this ModbusRegisters data)
{
if (data.ParseBool(1006, 1)) yield return "TaM1: BMS temperature high";
if (data.ParseBool(1006, 4)) yield return "TbM1: Battery temperature high";
if (data.ParseBool(1006, 6)) yield return "VBm1: Bus voltage low";
if (data.ParseBool(1006, 8)) yield return "VBM1: Bus voltage high";
if (data.ParseBool(1006, 10)) yield return "IDM1: Discharge current high";
if (data.ParseBool(1006, 24)) yield return "vsM1: String voltage high";
if (data.ParseBool(1006, 26)) yield return "iCM1: Charge current high";
if (data.ParseBool(1006, 28)) yield return "iDM1: Discharge current high";
if (data.ParseBool(1006, 30)) yield return "MID1: String voltages unbalanced";
if (data.ParseBool(1006, 32)) yield return "BLPW: Not enough charging power on bus";
if (data.ParseBool(1006, 35)) yield return "Ah_W: String SOC low";
if (data.ParseBool(1006, 38)) yield return "MPMM: Midpoint wiring problem";
if (data.ParseBool(1006, 39)) yield return "TCMM:";
if (data.ParseBool(1006, 40)) yield return "TCdi: Temperature difference between strings high";
if (data.ParseBool(1006, 41)) yield return "WMTO:";
if (data.ParseBool(1006, 44)) yield return "bit44:";
if (data.ParseBool(1006, 46)) yield return "CELL1:";
}
}

View File

@ -1,7 +1,8 @@
using DecimalMath;
using InnovEnergy.Lib.Protocols.Modbus.Clients; using InnovEnergy.Lib.Protocols.Modbus.Clients;
using InnovEnergy.Lib.Protocols.Modbus.Connections; using InnovEnergy.Lib.Protocols.Modbus.Connections;
using InnovEnergy.Lib.StatusApi.Connections; using InnovEnergy.Lib.StatusApi.Connections;
using InnovEnergy.Lib.StatusApi.Phases; using InnovEnergy.Lib.Units.Composite;
using InnovEnergy.Lib.Utils; using InnovEnergy.Lib.Utils;
using static DecimalMath.DecimalEx; using static DecimalMath.DecimalEx;
@ -29,7 +30,9 @@ public class EmuMeterDevice
return null; return null;
} }
} }
private static Decimal GetPhi(Decimal cosPhi) => cosPhi.Clamp(-1m, 1m).Apply(ACos);
//private static Decimal GetPhi(Decimal cosPhi) => cosPhi.Clamp(-1m, 1m).Apply(ACos);
private EmuMeterStatus TryReadStatus() private EmuMeterStatus TryReadStatus()
{ {
@ -75,45 +78,70 @@ public class EmuMeterDevice
var energyImportL3 = energyPhases[80 / 4] / 1000.0m; var energyImportL3 = energyPhases[80 / 4] / 1000.0m;
var energyExportL3 = energyPhases[100 / 4] / 1000.0m; var energyExportL3 = energyPhases[100 / 4] / 1000.0m;
// Ac: new Ac3Bus
// (
// new AcPhase(
// voltageL1N,
// currentL1,
// GetPhi(powerFactorL1)
// ),
//
// new AcPhase(
// voltageL2N,
// currentL2,
// GetPhi(powerFactorL2)
// ),
//
// new AcPhase(
// voltageL3N,
// currentL3,
// GetPhi(powerFactorL3)
// ),
// frequency
// ),
// activePowerL123,
// reactivePowerL123,
// apparentPowerL123,
// currentL123,
// voltageL1L2,
// voltageL2L3,
// voltageL3L1,
// energyImportL123,
// energyImportL1,
// energyImportL2,
// energyImportL3,
// energyExportL123,
// energyExportL1,
// energyExportL2,
// energyExportL3
// );
return new EmuMeterStatus return new EmuMeterStatus
( {
Ac: new ThreePhaseAcConnection Ac = new Ac3Bus
( {
new AcPhase( Frequency = frequency,
voltageL1N, L1 = new AcPhase
currentL1, {
GetPhi(powerFactorL1) Current = currentL1,
), Voltage = voltageL1N,
Phi = ATan2(reactivePowerL1, activePowerL1) // TODO: check that this works
new AcPhase( },
voltageL2N, L2 = new AcPhase
currentL2, {
GetPhi(powerFactorL2) Current = currentL2,
), Voltage = voltageL2N,
Phi = ATan2(reactivePowerL2, activePowerL2)
new AcPhase( },
voltageL3N, L3 = new AcPhase
currentL3, {
GetPhi(powerFactorL3) Current = currentL3,
), Voltage = voltageL3N,
frequency Phi = ATan2(reactivePowerL3, activePowerL3)
), }
activePowerL123, }
reactivePowerL123, };
apparentPowerL123,
currentL123,
voltageL1L2,
voltageL2L3,
voltageL3L1,
energyImportL123,
energyImportL1,
energyImportL2,
energyImportL3,
energyExportL123,
energyExportL1,
energyExportL2,
energyExportL3
);
} }
} }

View File

@ -1,26 +1,10 @@
using InnovEnergy.Lib.StatusApi.Connections; using InnovEnergy.Lib.StatusApi;
using InnovEnergy.Lib.StatusApi.Devices;
namespace InnovEnergy.Lib.Devices.EmuMeter; namespace InnovEnergy.Lib.Devices.EmuMeter;
public record EmuMeterStatus public record EmuMeterStatus : PowerMeterStatus
( {
ThreePhaseAcConnection Ac, // TODO add serial nb, (and other?)
Decimal ActivePowerL123, }
Decimal ReactivePowerL123,
Decimal ApparentPowerL123,
Decimal CurrentL123,
Decimal VoltageL1L2,
Decimal VoltageL2L3,
Decimal VoltageL3L1,
Decimal EnergyImportL123,
Decimal EnergyImportL1,
Decimal EnergyImportL2,
Decimal EnergyImportL3,
Decimal EnergyExportL123,
Decimal EnergyExportL1,
Decimal EnergyExportL2,
Decimal EnergyExportL3
):GridMeter(Ac)
{}

View File

@ -2,8 +2,7 @@ using System.Diagnostics.CodeAnalysis;
using InnovEnergy.Lib.Devices.Trumpf.TruConvert; using InnovEnergy.Lib.Devices.Trumpf.TruConvert;
using InnovEnergy.Lib.Protocols.Modbus.Clients; using InnovEnergy.Lib.Protocols.Modbus.Clients;
using InnovEnergy.Lib.Protocols.Modbus.Connections; using InnovEnergy.Lib.Protocols.Modbus.Connections;
using InnovEnergy.Lib.StatusApi.Connections; using InnovEnergy.Lib.Units.Composite;
using InnovEnergy.Lib.StatusApi.Phases;
using InnovEnergy.Lib.Utils; using InnovEnergy.Lib.Utils;
using static DecimalMath.DecimalEx; using static DecimalMath.DecimalEx;
using static InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.AcControlRegisters; using static InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.AcControlRegisters;
@ -216,7 +215,7 @@ public class TruConvertAcDevice
return new TruConvertAcStatus return new TruConvertAcStatus
( (
Ac: new ThreePhaseAcConnection Ac: new Ac3Bus
( (
new AcPhase(gridVoltageL1,phaseCurrentL1, ACos(powerAcL1/apparentPowerAcL1)), new AcPhase(gridVoltageL1,phaseCurrentL1, ACos(powerAcL1/apparentPowerAcL1)),
new AcPhase(gridVoltageL2,phaseCurrentL2, ACos(powerAcL2/apparentPowerAcL2)), new AcPhase(gridVoltageL2,phaseCurrentL2, ACos(powerAcL2/apparentPowerAcL2)),

View File

@ -1,6 +1,6 @@
using InnovEnergy.Lib.Devices.Trumpf.TruConvert; using InnovEnergy.Lib.Devices.Trumpf.TruConvert;
using InnovEnergy.Lib.StatusApi.Connections; using InnovEnergy.Lib.StatusApi.Connections;
using InnovEnergy.Lib.StatusApi.Devices; using InnovEnergy.Lib.Units.Composite;
namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc;
@ -9,7 +9,7 @@ using WarningMessages = IReadOnlyList<WarningMessage>;
public record TruConvertAcStatus public record TruConvertAcStatus
( (
ThreePhaseAcConnection Ac, Ac3Bus Ac,
DcConnection Dc, DcConnection Dc,
String SerialNumber, String SerialNumber,
MainState MainState, MainState MainState,

View File

@ -1,6 +1,7 @@
using InnovEnergy.Lib.Devices.Trumpf.TruConvert; using InnovEnergy.Lib.StatusApi;
using InnovEnergy.Lib.StatusApi.Connections; using InnovEnergy.Lib.Units;
using InnovEnergy.Lib.StatusApi.Devices; using InnovEnergy.Lib.Units.Composite;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc;
@ -8,20 +9,24 @@ using AlarmMessages = IReadOnlyList<AlarmMessage>;
using WarningMessages = IReadOnlyList<WarningMessage>; using WarningMessages = IReadOnlyList<WarningMessage>;
using DcCurrentLimitStates = IReadOnlyList<DcCurrentLimitState>; using DcCurrentLimitStates = IReadOnlyList<DcCurrentLimitState>;
public record TruConvertDcStatus public record TruConvertDcStatus
( (
DcConnection Dc, DcBus DcLeft,
MainState MainState, DcBus DcRight,
UInt16 NumberOfConnectedSlaves, State MainState,
UInt16 NumberOfConnectedSubSlaves, Power TotalDcPower, // TODO: necessary?
Decimal BatteryVoltage, State StatusOfCurrentLimiting,
Decimal BatteryCurrent, Decimal OverloadCapacity,
Decimal TotalDcPower, Temperature DcDcInletTemperature,
DcCurrentLimitStates StatusOfCurrentLimiting, State Alarms,
Decimal OverloadCapacity, State Warnings,
Decimal DcDcInletTemperature, State PowerOperation
AlarmMessages Alarms,
WarningMessages Warnings, // UInt16 NumberOfConnectedSlaves, // TODO: necessary?
Boolean PowerOperation // UInt16 NumberOfConnectedSubSlaves, // TODO: necessary?
):DcDevice(Dc) ) :
{} DcDcConverterStatus(DcLeft, DcRight)
{
public static TruConvertDcStatus operator |(TruConvertDcStatus left, TruConvertDcStatus right) => OpParallel(left, right);
private static readonly Func<TruConvertDcStatus, TruConvertDcStatus, TruConvertDcStatus> OpParallel = Operators.Op<TruConvertDcStatus>("|");
}

View File

@ -10,8 +10,8 @@ using T = BatteryStatus;
[OpParallel] [OpParallel]
public partial record BatteryStatus : DeviceStatus, IDcConnection public partial record BatteryStatus : DeviceStatus, IDcConnection
{ {
public required DcPhase Dc { get; init; } public DcBus Dc { get; init; }
public required Percent Soc { get; init; } public Percent Soc { get; init; }
public required Temperature Temperature { get; init; } public Temperature Temperature { get; init; }
} }

View File

@ -4,5 +4,5 @@ namespace InnovEnergy.Lib.StatusApi.Connections;
public interface IAc1Connection public interface IAc1Connection
{ {
Ac1Phase Ac { get; } Ac1Bus Ac { get; }
} }

View File

@ -4,5 +4,5 @@ namespace InnovEnergy.Lib.StatusApi.Connections;
public interface IAc3Connection public interface IAc3Connection
{ {
Ac3Phase Ac { get; } Ac3Bus Ac { get; }
} }

View File

@ -5,5 +5,5 @@ namespace InnovEnergy.Lib.StatusApi.Connections;
public interface IDcConnection public interface IDcConnection
{ {
DcPhase Dc { get; } DcBus Dc { get; }
} }

View File

@ -4,5 +4,5 @@ namespace InnovEnergy.Lib.StatusApi.Connections;
public interface IPvConnection public interface IPvConnection
{ {
IReadOnlyList<DcPhase> Strings { get; } IReadOnlyList<DcBus> Strings { get; }
} }

View File

@ -6,8 +6,8 @@ namespace InnovEnergy.Lib.StatusApi;
[OpParallel] [OpParallel]
public partial record DcDcConverterStatus : DeviceStatus public partial record DcDcConverterStatus : DeviceStatus
{ {
public required DcPhase Left { get; init; } public DcBus Left { get; init; }
public required DcPhase Right { get; init; } public DcBus Right { get; init; }
} }

View File

@ -1,12 +1,64 @@
using System.Text.Json;
using InnovEnergy.Lib.Utils; using InnovEnergy.Lib.Utils;
using static InnovEnergy.Lib.Units.Units;
namespace InnovEnergy.Lib.StatusApi; namespace InnovEnergy.Lib.StatusApi;
public abstract record DeviceStatus public abstract record DeviceStatus
{ {
private static readonly JsonSerializerOptions JsonSerializerOptions;
static DeviceStatus()
{
JsonSerializerOptions = new JsonSerializerOptions { WriteIndented = true };
JsonConverters.ForEach(JsonSerializerOptions.Converters.Add); // how stupid is that?!!
}
public String DeviceType => GetType() public String DeviceType => GetType()
.Generate(t => t.BaseType!) .Generate(t => t.BaseType!)
.First(t => t.IsAbstract) .First(t => t.IsAbstract)
.Name .Name
.Replace("Status", ""); .Replace("Status", "");
}
public String ToJson() => JsonSerializer.Serialize(this, GetType(), JsonSerializerOptions);
}
// public static class Program
// {
// public static void Main(string[] args)
// {
// var x = new ThreePhasePvInverterStatus
// {
// Ac = new()
// {
// Frequency = 50,
// L1 = new()
// {
// Current = 10,
// Voltage = 10,
// Phi = 0,
// },
// L2 = new()
// {
// Current = 52,
// Voltage = 220,
// Phi = Angle.Pi / 2,
// },
// L3 = new()
// {
// Current = 158,
// Voltage = 454,
// Phi = Angle.Pi / 3,
// },
// },
// Strings = new DcBus[]
// {
// new() { Current = 10, Voltage = 22 },
// new() { Current = 12, Voltage = 33 },
// }
// };
//
// var s = x.ToJson();
// }
// }

View File

@ -7,8 +7,8 @@ namespace InnovEnergy.Lib.StatusApi;
[OpParallel] [OpParallel]
public partial record MpptStatus : IDcConnection, IPvConnection public partial record MpptStatus : IDcConnection, IPvConnection
{ {
public required DcPhase Dc { get; init; } public DcBus Dc { get; init; }
public required IReadOnlyList<DcPhase> Strings { get; init; } public IReadOnlyList<DcBus> Strings { get; init; }
} }

View File

@ -7,5 +7,5 @@ namespace InnovEnergy.Lib.StatusApi;
[OpParallel] [OpParallel]
public partial record PowerMeterStatus : DeviceStatus, IAc3Connection public partial record PowerMeterStatus : DeviceStatus, IAc3Connection
{ {
public required Ac3Phase Ac { get; init; } public Ac3Bus Ac { get; init; }
} }

View File

@ -10,6 +10,6 @@ public partial record SinglePhaseInverterStatus :
IAc1Connection, IAc1Connection,
IDcConnection IDcConnection
{ {
public required Ac1Phase Ac { get; init; } public Ac1Bus Ac { get; init; }
public required DcPhase Dc { get; init; } public DcBus Dc { get; init; }
} }

View File

@ -10,6 +10,6 @@ public partial record SinglePhasePvInverterStatus :
IAc1Connection, IAc1Connection,
IPvConnection IPvConnection
{ {
public required Ac1Phase Ac { get; init; } public Ac1Bus Ac { get; init; }
public required IReadOnlyList<DcPhase> Strings { get; init; } public IReadOnlyList<DcBus> Strings { get; init; }
} }

View File

@ -1,5 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="../InnovEnergy.Lib.props" /> <Import Project="../InnovEnergy.Lib.props" />
<!-- <Import Project="../../App/InnovEnergy.App.props" />-->
<ItemGroup> <ItemGroup>
<ProjectReference Include="../Protocols/Modbus/Modbus.csproj" /> <ProjectReference Include="../Protocols/Modbus/Modbus.csproj" />

View File

@ -10,7 +10,7 @@ public partial record ThreePhaseInverterStatus :
IAc3Connection, IAc3Connection,
IDcConnection IDcConnection
{ {
public required Ac3Phase Ac { get; init; } public Ac3Bus Ac { get; init; }
public required DcPhase Dc { get; init; } public DcBus Dc { get; init; }
} }

View File

@ -10,6 +10,6 @@ public partial record ThreePhasePvInverterStatus :
IAc3Connection, IAc3Connection,
IPvConnection IPvConnection
{ {
public required Ac3Phase Ac { get; init; } public Ac3Bus Ac { get; init; }
public required IReadOnlyList<DcPhase> Strings { get; init; } public IReadOnlyList<DcBus> Strings { get; init; }
} }

View File

@ -2,6 +2,9 @@
#define Sum #define Sum
using static System.Math; using static System.Math;
using System.Text.Json;
using System.Text.Json.Serialization;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Units; namespace InnovEnergy.Lib.Units;
@ -10,7 +13,7 @@ using T = Angle;
public readonly partial struct Angle public readonly partial struct Angle
{ {
public Decimal Value { get; } public Decimal Value { get; }
public override String ToString() => Value + Unit; public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
// scalar multiplication // scalar multiplication
@ -75,3 +78,19 @@ public readonly partial struct Angle
public override Int32 GetHashCode() => Value.GetHashCode(); public override Int32 GetHashCode() => Value.GetHashCode();
} }
internal class AngleConverter : JsonConverter<Angle>
{
public override Angle Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new Angle(reader.GetDecimal());
}
public override void Write(Utf8JsonWriter writer, Angle value, JsonSerializerOptions options)
{
var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits);
writer.WriteNumberValue(rounded);
}
}

View File

@ -2,6 +2,9 @@
#define Sum #define Sum
using static System.Math; using static System.Math;
using System.Text.Json;
using System.Text.Json.Serialization;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Units; namespace InnovEnergy.Lib.Units;
@ -10,7 +13,7 @@ using T = ApparentPower;
public readonly partial struct ApparentPower public readonly partial struct ApparentPower
{ {
public Decimal Value { get; } public Decimal Value { get; }
public override String ToString() => Value + Unit; public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
// scalar multiplication // scalar multiplication
@ -75,3 +78,19 @@ public readonly partial struct ApparentPower
public override Int32 GetHashCode() => Value.GetHashCode(); public override Int32 GetHashCode() => Value.GetHashCode();
} }
internal class ApparentPowerConverter : JsonConverter<ApparentPower>
{
public override ApparentPower Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new ApparentPower(reader.GetDecimal());
}
public override void Write(Utf8JsonWriter writer, ApparentPower value, JsonSerializerOptions options)
{
var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits);
writer.WriteNumberValue(rounded);
}
}

View File

@ -1,19 +1,18 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
namespace InnovEnergy.Lib.Units.Composite; namespace InnovEnergy.Lib.Units.Composite;
public record Ac1Phase : AcPhase public record Ac1Bus : AcPhase
{ {
public required Frequency Frequency { get; init; } public Frequency Frequency { get; init; }
[SuppressMessage("ReSharper", "RedundantCast")] [SuppressMessage("ReSharper", "RedundantCast")]
public static Ac1Phase operator |(Ac1Phase left, Ac1Phase right) public static Ac1Bus operator |(Ac1Bus left, Ac1Bus right)
{ {
var f = left.Frequency | right.Frequency; var f = left.Frequency | right.Frequency;
var p = (AcPhase)left | (AcPhase)right; var p = (AcPhase)left | (AcPhase)right;
return new Ac1Phase return new Ac1Bus
{ {
Frequency = f, Frequency = f,
Current = p.Current, Current = p.Current,
@ -21,7 +20,6 @@ public record Ac1Phase : AcPhase
Phi = p.Phi Phi = p.Phi
}; };
} }
} }

View File

@ -3,20 +3,18 @@ using static DecimalMath.DecimalEx;
namespace InnovEnergy.Lib.Units.Composite; namespace InnovEnergy.Lib.Units.Composite;
public record Ac3Phase public record Ac3Bus
{ {
public required AcPhase L1 { get; init; } public AcPhase L1 { get; init; }
public required AcPhase L2 { get; init; } public AcPhase L2 { get; init; }
public required AcPhase L3 { get; init; } public AcPhase L3 { get; init; }
public required Frequency Frequency { get; init; } public Frequency Frequency { get; init; }
public ApparentPower ApparentPower => L1.ApparentPower + L2.ApparentPower + L3.ApparentPower; public ApparentPower ApparentPower => L1.ApparentPower + L2.ApparentPower + L3.ApparentPower;
public ReactivePower ReactivePower => L1.ReactivePower + L2.ReactivePower + L3.ReactivePower; public ReactivePower ReactivePower => L1.ReactivePower + L2.ReactivePower + L3.ReactivePower;
public Power ActivePower => L1.ActivePower + L2.ActivePower + L3.ActivePower; public Power ActivePower => L1.ActivePower + L2.ActivePower + L3.ActivePower;
public Angle Phi => ATan2(ReactivePower, ActivePower); public Angle Phi => ATan2(ReactivePower, ActivePower);
public static Ac3Phase operator |(Ac3Phase left, Ac3Phase right) => OpParallel(left, right); public static Ac3Bus operator |(Ac3Bus left, Ac3Bus right) => OpParallel(left, right);
private static readonly Func<Ac3Phase, Ac3Phase, Ac3Phase> OpParallel = "|".CreateBinaryOpForProps<Ac3Phase>(); private static readonly Func<Ac3Bus, Ac3Bus, Ac3Bus> OpParallel = "|".CreateBinaryOpForProps<Ac3Bus>();
} }

View File

@ -3,28 +3,28 @@ using static DecimalMath.DecimalEx;
namespace InnovEnergy.Lib.Units.Composite; namespace InnovEnergy.Lib.Units.Composite;
public record AcPhase : IPhase public record AcPhase : IBus
{ {
private readonly Voltage _Voltage; private readonly Voltage _Voltage;
public required Voltage Voltage public Voltage Voltage
{ {
get => _Voltage; get => _Voltage;
init => _Voltage = value >= 0 ? value : throw new ArgumentException("RMS value cannot be negative"); init => _Voltage = value >= 0m ? value : throw new ArgumentException("RMS value cannot be negative");
} }
private readonly Current _Current; private readonly Current _Current;
public required Current Current public Current Current
{ {
get => _Current; get => _Current;
init => _Current = value >= 0 ? value : throw new ArgumentException("RMS value cannot be negative"); init => _Current = value >= 0m ? value : throw new ArgumentException("RMS value cannot be negative");
} }
public required Angle Phi { get; init; } public Angle Phi { get; init; }
public ApparentPower ApparentPower => Voltage.Value * Current.Value ; public ApparentPower ApparentPower => Voltage.Value * Current.Value ;
public Power ActivePower => ApparentPower.Value * PowerFactor; public Power ActivePower => ApparentPower.Value * PowerFactor.Value;
public ReactivePower ReactivePower => ApparentPower.Value * Sin(Phi); public ReactivePower ReactivePower => ApparentPower.Value * Sin(Phi);
public Decimal PowerFactor => Cos(Phi); public Number PowerFactor => Cos(Phi);
public static AcPhase operator |(AcPhase left, AcPhase right) public static AcPhase operator |(AcPhase left, AcPhase right)

View File

@ -0,0 +1,14 @@
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Units.Composite;
public record DcBus : IBus
{
public Voltage Voltage { get; init; }
public Current Current { get; init; }
public Power Power => Current * Voltage;
public static DcBus operator |(DcBus left, DcBus right) => OpParallel(left, right);
private static readonly Func<DcBus, DcBus, DcBus> OpParallel = "|".CreateBinaryOpForProps<DcBus>();
}

View File

@ -1,16 +0,0 @@
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Units.Composite;
public record DcPhase : IPhase
{
public required Voltage Voltage { get; init;}
public required Current Current { get; init;}
public Power Power => Current * Voltage;
public static DcPhase operator |(DcPhase left, DcPhase right) => OpParallel(left, right);
private static readonly Func<DcPhase, DcPhase, DcPhase> OpParallel = "|".CreateBinaryOpForProps<DcPhase>();
}

View File

@ -4,7 +4,7 @@ namespace InnovEnergy.Lib.Units.Composite;
[SuppressMessage("ReSharper", "MemberCanBeProtected.Global")] [SuppressMessage("ReSharper", "MemberCanBeProtected.Global")]
public interface IPhase public interface IBus
{ {
public Voltage Voltage { get; } public Voltage Voltage { get; }
public Current Current { get; } public Current Current { get; }

View File

@ -2,6 +2,9 @@
#define Sum #define Sum
using static System.Math; using static System.Math;
using System.Text.Json;
using System.Text.Json.Serialization;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Units; namespace InnovEnergy.Lib.Units;
@ -10,7 +13,7 @@ using T = Current;
public readonly partial struct Current public readonly partial struct Current
{ {
public Decimal Value { get; } public Decimal Value { get; }
public override String ToString() => Value + Unit; public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
// scalar multiplication // scalar multiplication
@ -75,3 +78,19 @@ public readonly partial struct Current
public override Int32 GetHashCode() => Value.GetHashCode(); public override Int32 GetHashCode() => Value.GetHashCode();
} }
internal class CurrentConverter : JsonConverter<Current>
{
public override Current Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new Current(reader.GetDecimal());
}
public override void Write(Utf8JsonWriter writer, Current value, JsonSerializerOptions options)
{
var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits);
writer.WriteNumberValue(rounded);
}
}

View File

@ -2,6 +2,9 @@
#define Equal #define Equal
using static System.Math; using static System.Math;
using System.Text.Json;
using System.Text.Json.Serialization;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Units; namespace InnovEnergy.Lib.Units;
@ -10,7 +13,7 @@ using T = Frequency;
public readonly partial struct Frequency public readonly partial struct Frequency
{ {
public Decimal Value { get; } public Decimal Value { get; }
public override String ToString() => Value + Unit; public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
// scalar multiplication // scalar multiplication
@ -75,3 +78,19 @@ public readonly partial struct Frequency
public override Int32 GetHashCode() => Value.GetHashCode(); public override Int32 GetHashCode() => Value.GetHashCode();
} }
internal class FrequencyConverter : JsonConverter<Frequency>
{
public override Frequency Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new Frequency(reader.GetDecimal());
}
public override void Write(Utf8JsonWriter writer, Frequency value, JsonSerializerOptions options)
{
var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits);
writer.WriteNumberValue(rounded);
}
}

View File

@ -1,7 +1,10 @@
#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. #nullable enable // Auto-generated code requires an explicit '#nullable' directive in source.
#define Type #define AggregationType
using static System.Math; using static System.Math;
using System.Text.Json;
using System.Text.Json.Serialization;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Units; namespace InnovEnergy.Lib.Units;
@ -10,7 +13,7 @@ using T = Template;
public readonly partial struct Template public readonly partial struct Template
{ {
public Decimal Value { get; } public Decimal Value { get; }
public override String ToString() => Value + Unit; public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
// scalar multiplication // scalar multiplication
@ -75,3 +78,19 @@ public readonly partial struct Template
public override Int32 GetHashCode() => Value.GetHashCode(); public override Int32 GetHashCode() => Value.GetHashCode();
} }
internal class TemplateConverter : JsonConverter<Template>
{
public override Template Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new Template(reader.GetDecimal());
}
public override void Write(Utf8JsonWriter writer, Template value, JsonSerializerOptions options)
{
var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits);
writer.WriteNumberValue(rounded);
}
}

View File

@ -11,5 +11,5 @@ do
file=$(basename -- "$path") file=$(basename -- "$path")
class="${file%.*}" class="${file%.*}"
echo "generating $file" echo "generating $file"
sed "s/Template/$class/g; s/Type/$type/" "./Generator/Template.txt" > "./$class.generated.cs" sed "s/Template/$class/g; s/AggregationType/$type/g" "./Generator/Template.txt" > "./$class.generated.cs"
done done

View File

@ -1,17 +0,0 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace InnovEnergy.Lib.Units.Json;
public class CurrentConverter : JsonConverter<Current>
{
public override Current Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new Current(reader.GetDecimal());
}
public override void Write(Utf8JsonWriter writer, Current value, JsonSerializerOptions options)
{
writer.WriteNumberValue(value.Value);
}
}

View File

@ -1,17 +0,0 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace InnovEnergy.Lib.Units.Json;
public class PowerConverter : JsonConverter<Power>
{
public override Power Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new Power(reader.GetDecimal());
}
public override void Write(Utf8JsonWriter writer, Power value, JsonSerializerOptions options)
{
writer.WriteNumberValue(value.Value);
}
}

View File

@ -1,17 +0,0 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace InnovEnergy.Lib.Units.Json;
public class ResistanceConverter : JsonConverter<Current>
{
public override Current Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new Current(reader.GetDecimal());
}
public override void Write(Utf8JsonWriter writer, Current value, JsonSerializerOptions options)
{
writer.WriteNumberValue(value.Value);
}
}

View File

@ -1,17 +0,0 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace InnovEnergy.Lib.Units.Json;
public class VoltageConverter : JsonConverter<Voltage>
{
public override Voltage Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new Voltage(reader.GetDecimal());
}
public override void Write(Utf8JsonWriter writer, Voltage value, JsonSerializerOptions options)
{
writer.WriteNumberValue(value.Value);
}
}

View File

@ -0,0 +1,13 @@
using InnovEnergy.Lib.Units.Generator;
namespace InnovEnergy.Lib.Units;
[Sum]
public readonly partial struct Number
{
public static String Unit => "";
public static String Symbol => "";
public Number(Decimal value) => Value = value;
}

View File

@ -0,0 +1,96 @@
#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source.
#define Sum
using static System.Math;
using System.Text.Json;
using System.Text.Json.Serialization;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Units;
using T = Number;
public readonly partial struct Number
{
public Decimal Value { get; }
public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
// scalar multiplication
public static T operator *(Decimal scalar, T t) => new T(scalar * t.Value);
public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value);
public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar);
// parallel
#if Sum
public static T operator |(T left, T right) => new T(left.Value + right.Value);
public static T operator -(T t) => new T(-t.Value);
#elif Mean
public static T operator |(T left, T right) => new T((left.Value + right.Value)/2m);
#elif Equal
public static T operator |(T left, T right)
{
var d = Max(Abs(left.Value), Abs(right.Value));
if (d == 0m)
return new T(0m);
var relativeError = Abs(left.Value - right.Value) / d;
const Decimal maxRelativeError = 0.05m;
if (relativeError > maxRelativeError)
throw new Exception($"{nameof(left)} and {nameof(right)} must be approximately equal.\n" +
$"Difference > {maxRelativeError * 100}% detected\n" +
$"{nameof(left)} : {left}\n" +
$"{nameof(right)}: {right}");
return new T((left.Value + right.Value) / 2m);
}
#endif
// compare
public static Boolean operator ==(T left, T right) => left.Value == right.Value;
public static Boolean operator !=(T left, T right) => left.Value != right.Value;
public static Boolean operator > (T left, T right) => left.Value > right.Value;
public static Boolean operator < (T left, T right) => left.Value < right.Value;
public static Boolean operator >=(T left, T right) => left.Value >= right.Value;
public static Boolean operator <=(T left, T right) => left.Value <= right.Value;
// conversion
public static implicit operator T(Decimal d) => new T(d);
public static implicit operator T(Double d) => new T((Decimal)d);
public static implicit operator T(Int32 i) => new T(i);
public static implicit operator Decimal(T t) => t.Value;
// equality
public Boolean Equals(T other) => Value == other.Value;
public override Boolean Equals(Object? obj) => obj is T other && Equals(other);
public override Int32 GetHashCode() => Value.GetHashCode();
}
internal class NumberConverter : JsonConverter<Number>
{
public override Number Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new Number(reader.GetDecimal());
}
public override void Write(Utf8JsonWriter writer, Number value, JsonSerializerOptions options)
{
var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits);
writer.WriteNumberValue(rounded);
}
}

View File

@ -10,6 +10,7 @@ public readonly struct Percent
public Percent(Decimal value) => Value = value; public Percent(Decimal value) => Value = value;
// not generated // not generated
// TODO: generate?
public Decimal Value { get; } public Decimal Value { get; }
public override String ToString() => Value + Unit; public override String ToString() => Value + Unit;
@ -20,4 +21,23 @@ public readonly struct Percent
// parallel // parallel
public static Percent operator |(T left, T right) => new T((left.Value + right.Value) / 2m); public static Percent operator |(T left, T right) => new T((left.Value + right.Value) / 2m);
// compare
public static Boolean operator ==(T left, T right) => left.Value == right.Value;
public static Boolean operator !=(T left, T right) => left.Value != right.Value;
public static Boolean operator > (T left, T right) => left.Value > right.Value;
public static Boolean operator < (T left, T right) => left.Value < right.Value;
public static Boolean operator >=(T left, T right) => left.Value >= right.Value;
public static Boolean operator <=(T left, T right) => left.Value <= right.Value;
// conversion
public static implicit operator T(Decimal d) => new T(d);
public static implicit operator T(Double d) => new T((Decimal)d);
public static implicit operator T(Int32 i) => new T(i);
public static implicit operator Decimal(T t) => t.Value;
// equality
public Boolean Equals(T other) => Value == other.Value;
public override Boolean Equals(Object? obj) => obj is T other && Equals(other);
public override Int32 GetHashCode() => Value.GetHashCode();
} }

View File

@ -10,7 +10,6 @@ public readonly partial struct Power
public static String Symbol => "P"; public static String Symbol => "P";
public Power(Decimal value) => Value = value; public Power(Decimal value) => Value = value;
// P=UI // P=UI
public static Voltage operator /(Power power, Current current) => new Voltage(power.Value / current.Value); public static Voltage operator /(Power power, Current current) => new Voltage(power.Value / current.Value);

View File

@ -2,6 +2,9 @@
#define Sum #define Sum
using static System.Math; using static System.Math;
using System.Text.Json;
using System.Text.Json.Serialization;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Units; namespace InnovEnergy.Lib.Units;
@ -10,7 +13,7 @@ using T = Power;
public readonly partial struct Power public readonly partial struct Power
{ {
public Decimal Value { get; } public Decimal Value { get; }
public override String ToString() => Value + Unit; public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
// scalar multiplication // scalar multiplication
@ -75,3 +78,19 @@ public readonly partial struct Power
public override Int32 GetHashCode() => Value.GetHashCode(); public override Int32 GetHashCode() => Value.GetHashCode();
} }
internal class PowerConverter : JsonConverter<Power>
{
public override Power Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new Power(reader.GetDecimal());
}
public override void Write(Utf8JsonWriter writer, Power value, JsonSerializerOptions options)
{
var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits);
writer.WriteNumberValue(rounded);
}
}

View File

@ -2,6 +2,9 @@
#define Sum #define Sum
using static System.Math; using static System.Math;
using System.Text.Json;
using System.Text.Json.Serialization;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Units; namespace InnovEnergy.Lib.Units;
@ -10,7 +13,7 @@ using T = ReactivePower;
public readonly partial struct ReactivePower public readonly partial struct ReactivePower
{ {
public Decimal Value { get; } public Decimal Value { get; }
public override String ToString() => Value + Unit; public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
// scalar multiplication // scalar multiplication
@ -75,3 +78,19 @@ public readonly partial struct ReactivePower
public override Int32 GetHashCode() => Value.GetHashCode(); public override Int32 GetHashCode() => Value.GetHashCode();
} }
internal class ReactivePowerConverter : JsonConverter<ReactivePower>
{
public override ReactivePower Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new ReactivePower(reader.GetDecimal());
}
public override void Write(Utf8JsonWriter writer, ReactivePower value, JsonSerializerOptions options)
{
var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits);
writer.WriteNumberValue(rounded);
}
}

View File

@ -2,6 +2,9 @@
#define Sum #define Sum
using static System.Math; using static System.Math;
using System.Text.Json;
using System.Text.Json.Serialization;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Units; namespace InnovEnergy.Lib.Units;
@ -10,7 +13,7 @@ using T = Resistance;
public readonly partial struct Resistance public readonly partial struct Resistance
{ {
public Decimal Value { get; } public Decimal Value { get; }
public override String ToString() => Value + Unit; public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
// scalar multiplication // scalar multiplication
@ -75,3 +78,19 @@ public readonly partial struct Resistance
public override Int32 GetHashCode() => Value.GetHashCode(); public override Int32 GetHashCode() => Value.GetHashCode();
} }
internal class ResistanceConverter : JsonConverter<Resistance>
{
public override Resistance Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new Resistance(reader.GetDecimal());
}
public override void Write(Utf8JsonWriter writer, Resistance value, JsonSerializerOptions options)
{
var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits);
writer.WriteNumberValue(rounded);
}
}

View File

@ -1,6 +1,8 @@
using System.Collections;
namespace InnovEnergy.Lib.Units; namespace InnovEnergy.Lib.Units;
public readonly struct State public readonly struct State : IReadOnlyList<String>
{ {
public IReadOnlyList<String> Values { get; } public IReadOnlyList<String> Values { get; }
@ -13,13 +15,23 @@ public readonly struct State
} }
public State(params String[] values) : this((IReadOnlyList<String>)values){} public State(params String[] values) : this((IReadOnlyList<String>)values){}
public State(params State[] states) : this(states.SelectMany(s => s.Values).ToList()){} public State(params State[] states) : this((IReadOnlyList<String>)states.SelectMany(s => s.Values).ToList()){}
public static implicit operator State(String s) => new State(s); public static implicit operator State(String s) => new(s);
public static implicit operator State(Enum e) => new State(e.ToString()); public static implicit operator State(Enum e) => new(e.ToString());
public static implicit operator State(Boolean s) => new State(s.ToString()); public static implicit operator State(Boolean s) => new(s.ToString());
public static implicit operator State(List<String> s) => new((IReadOnlyList<String>)s);
public static implicit operator State(String[] s) => new((IReadOnlyList<String>)s);
public static implicit operator State(List<Enum> es) => new(es.Select(e => e.ToString()).ToList());
public static implicit operator State(Enum[] es) => new(es.Select(e => e.ToString()).ToList());
public static State operator |(State left, State right) => new State(left, right); public static State operator |(State left, State right) => new(left, right);
public IEnumerator<String> GetEnumerator() => Values.GetEnumerator();
public override String ToString() => String.Join("; ", Values); public override String ToString() => String.Join("; ", Values);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public Int32 Count => Values.Count;
public String this[Int32 index] => Values[index];
} }

View File

@ -2,6 +2,9 @@
#define Mean #define Mean
using static System.Math; using static System.Math;
using System.Text.Json;
using System.Text.Json.Serialization;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Units; namespace InnovEnergy.Lib.Units;
@ -10,7 +13,7 @@ using T = Temperature;
public readonly partial struct Temperature public readonly partial struct Temperature
{ {
public Decimal Value { get; } public Decimal Value { get; }
public override String ToString() => Value + Unit; public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
// scalar multiplication // scalar multiplication
@ -75,3 +78,19 @@ public readonly partial struct Temperature
public override Int32 GetHashCode() => Value.GetHashCode(); public override Int32 GetHashCode() => Value.GetHashCode();
} }
internal class TemperatureConverter : JsonConverter<Temperature>
{
public override Temperature Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new Temperature(reader.GetDecimal());
}
public override void Write(Utf8JsonWriter writer, Temperature value, JsonSerializerOptions options)
{
var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits);
writer.WriteNumberValue(rounded);
}
}

View File

@ -1,10 +1,27 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using InnovEnergy.Lib.Units.Json;
namespace InnovEnergy.Lib.Units; namespace InnovEnergy.Lib.Units;
public static class Units public static class Units
{ {
static Units()
{
JsonConverters = typeof(Units)
.Assembly
.GetTypes()
.Where(t => t.IsAssignableTo(typeof(JsonConverter)))
.Select(Activator.CreateInstance)
.Cast<JsonConverter>()
.ToArray();
}
public static IReadOnlyList<JsonConverter> JsonConverters { get; }
public static Byte DisplaySignificantDigits { get; set; } = 3;
public static Byte JsonSignificantDigits { get; set; } = 3;
public const Decimal MaxRelativeError = 0.05m; // 5% public const Decimal MaxRelativeError = 0.05m; // 5%
public static Current A (this Decimal value) => new Current(value); public static Current A (this Decimal value) => new Current(value);
@ -16,14 +33,6 @@ public static class Units
public static Frequency Hz (this Decimal value) => new Frequency(value); public static Frequency Hz (this Decimal value) => new Frequency(value);
public static Angle Rad (this Decimal value) => new Angle(value); public static Angle Rad (this Decimal value) => new Angle(value);
public static Temperature Celsius(this Decimal value) => new Temperature(value); public static Temperature Celsius(this Decimal value) => new Temperature(value);
public static readonly IReadOnlyList<JsonConverter> JsonConverters = new JsonConverter[]
{
new CurrentConverter(),
new VoltageConverter(),
new PowerConverter(),
new ResistanceConverter(),
// TODO
};
} }

View File

@ -2,6 +2,9 @@
#define Equal #define Equal
using static System.Math; using static System.Math;
using System.Text.Json;
using System.Text.Json.Serialization;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Units; namespace InnovEnergy.Lib.Units;
@ -10,7 +13,7 @@ using T = Voltage;
public readonly partial struct Voltage public readonly partial struct Voltage
{ {
public Decimal Value { get; } public Decimal Value { get; }
public override String ToString() => Value + Unit; public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
// scalar multiplication // scalar multiplication
@ -75,3 +78,19 @@ public readonly partial struct Voltage
public override Int32 GetHashCode() => Value.GetHashCode(); public override Int32 GetHashCode() => Value.GetHashCode();
} }
internal class VoltageConverter : JsonConverter<Voltage>
{
public override Voltage Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new Voltage(reader.GetDecimal());
}
public override void Write(Utf8JsonWriter writer, Voltage value, JsonSerializerOptions options)
{
var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits);
writer.WriteNumberValue(rounded);
}
}

View File

@ -1,32 +1,36 @@
using DecimalMath; using DecimalMath;
using static System.Math;
using static DecimalMath.DecimalEx;
namespace InnovEnergy.Lib.Utils; namespace InnovEnergy.Lib.Utils;
public static class DecimalUtils public static class DecimalUtils
{ {
public static Double RoundToSignificantFigures(this Double num, Int32 n) public static Double RoundToSignificantDigits(this Double num, Int32 n)
{ {
if (num == 0) if (num == 0)
return 0; return 0;
var d = Math.Ceiling(Math.Log10(num < 0 ? -num : num)); var d = Ceiling(Log10(num < 0 ? -num : num));
var power = n - (Int32)d; var power = n - (Int32)d;
var magnitude = Math.Pow(10, power); var magnitude = Math.Pow(10, power);
var shifted = Math.Round(num * magnitude); var shifted = Round(num * magnitude);
return shifted / magnitude; return shifted / magnitude;
} }
public static Decimal RoundToSignificantFigures(this Decimal num, Int32 n) public static Decimal RoundToSignificantDigits(this Decimal num, Int32 n)
{ {
if (num == 0) if (num == 0)
return 0; return 0;
var d = Math.Ceiling(DecimalEx.Log10(num < 0 ? -num : num)); var d = Ceiling(Log10(num < 0 ? -num : num));
var power = n - (Int32)d; var power = n - (Int32)d;
var magnitude = DecimalEx.Pow(10, power); var magnitude = DecimalEx.Pow(10, power);
var shifted = Math.Round(num * magnitude); var shifted = Round(num * magnitude);
return shifted / magnitude; return shifted / magnitude;
} }
} }

View File

@ -106,7 +106,7 @@ public static class EnumerableUtils
public static IEnumerable<T> NullableToEnumerable<T>(this T? t) public static IEnumerable<T> NullableToEnumerable<T>(this T? t)
{ {
if (t is not null) if (t is not null)
yield return t!; yield return t;
} }
public static IEnumerable<(T left, T right)> Pairwise<T>(this IEnumerable<T> ts) public static IEnumerable<(T left, T right)> Pairwise<T>(this IEnumerable<T> ts)
@ -246,7 +246,7 @@ public static class EnumerableUtils
public static IEnumerable<T> Generate<T>(this T seed, Func<T, T> next) public static IEnumerable<T> Generate<T>(this T seed, Func<T, T> next)
{ {
var value = seed; var value = seed;
while (true) while (value is not null)
{ {
yield return value; yield return value;
value = next(value); value = next(value);

View File

@ -2,11 +2,6 @@
<Import Project="../../InnovEnergy.Lib.props" /> <Import Project="../../InnovEnergy.Lib.props" />
<PropertyGroup>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Reactive" Version="5.0.0" /> <PackageReference Include="System.Reactive" Version="5.0.0" />
</ItemGroup> </ItemGroup>

View File

@ -6,11 +6,6 @@
<DebugType>full</DebugType> <DebugType>full</DebugType>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="../../Utils/Utils.csproj" /> <ProjectReference Include="../../Utils/Utils.csproj" />
<ProjectReference Include="../../Time/Time.csproj" /> <ProjectReference Include="../../Time/Time.csproj" />