Merge branch 'main' of https://git.innov.energy/Innovenergy/git_trunk
This commit is contained in:
commit
8dd591d752
|
@ -6,9 +6,6 @@
|
|||
<ProjectReference Include="../../Lib/Victron/VictronVRM/VictronVRM.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="RemoteSupportConsole.csproj.DotSettings" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CliWrap" Version="3.6.0" />
|
||||
|
|
|
@ -6,6 +6,6 @@ public static class Utils
|
|||
{
|
||||
public static Decimal Round3(this Decimal d)
|
||||
{
|
||||
return DecimalUtils.RoundToSignificantFigures(d, 3);
|
||||
return DecimalUtils.RoundToSignificantDigits(d, 3);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
using InnovEnergy.Lib.StatusApi;
|
||||
using InnovEnergy.Lib.StatusApi.Connections;
|
||||
using InnovEnergy.Lib.StatusApi.Devices;
|
||||
|
||||
namespace InnovEnergy.Lib.Devices.AMPT;
|
||||
|
||||
|
@ -11,5 +11,5 @@ public record AmptDeviceStatus
|
|||
UInt32 Timestamp, // The UTC timestamp of the measurements
|
||||
Decimal ProductionToday, // converted to kW in AmptCU class
|
||||
IReadOnlyList<DcConnection> Strings
|
||||
): Mppt(Dc, Strings)
|
||||
): MpptStatus(Dc, Strings)
|
||||
{}
|
|
@ -1,7 +1,5 @@
|
|||
using InnovEnergy.Lib.Protocols.Modbus.Clients;
|
||||
using InnovEnergy.Lib.Protocols.Modbus.Connections;
|
||||
using InnovEnergy.Lib.Protocols.Modbus.Conversions;
|
||||
using InnovEnergy.Lib.StatusApi.Connections;
|
||||
|
||||
namespace InnovEnergy.Lib.Devices.Battery48TL;
|
||||
|
||||
|
@ -33,18 +31,11 @@ public class Battery48TlDevice
|
|||
|
||||
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
|
||||
{
|
||||
var registers = Modbus.ReadInputRegisters(Constants.BaseAddress, Constants.NoOfRegisters);
|
||||
return TryReadStatus(registers);
|
||||
return Modbus
|
||||
.ReadInputRegisters(Constants.BaseAddress, Constants.NoOfRegisters)
|
||||
.ParseBatteryStatus();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -53,92 +44,4 @@ public class Battery48TlDevice
|
|||
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()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,39 +1,50 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using InnovEnergy.Lib.Protocols.Modbus.Conversions;
|
||||
using InnovEnergy.Lib.StatusApi.Connections;
|
||||
using InnovEnergy.Lib.StatusApi.Devices;
|
||||
using InnovEnergy.Lib.StatusApi;
|
||||
using InnovEnergy.Lib.Units;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
|
||||
namespace InnovEnergy.Lib.Devices.Battery48TL;
|
||||
|
||||
using T = Battery48TLStatus;
|
||||
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public record Battery48TLStatus
|
||||
(
|
||||
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 record Battery48TLStatus : BatteryStatus
|
||||
{
|
||||
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; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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:";
|
||||
}
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
using DecimalMath;
|
||||
using InnovEnergy.Lib.Protocols.Modbus.Clients;
|
||||
using InnovEnergy.Lib.Protocols.Modbus.Connections;
|
||||
using InnovEnergy.Lib.StatusApi.Connections;
|
||||
using InnovEnergy.Lib.StatusApi.Phases;
|
||||
using InnovEnergy.Lib.Units.Composite;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
using static DecimalMath.DecimalEx;
|
||||
|
||||
|
@ -29,7 +30,9 @@ public class EmuMeterDevice
|
|||
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()
|
||||
{
|
||||
|
@ -75,45 +78,70 @@ public class EmuMeterDevice
|
|||
var energyImportL3 = energyPhases[80 / 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
|
||||
(
|
||||
Ac: new ThreePhaseAcConnection
|
||||
(
|
||||
new AcPhase(
|
||||
voltageL1N,
|
||||
currentL1,
|
||||
GetPhi(powerFactorL1)
|
||||
),
|
||||
{
|
||||
Ac = new Ac3Bus
|
||||
{
|
||||
Frequency = frequency,
|
||||
L1 = new AcPhase
|
||||
{
|
||||
Current = currentL1,
|
||||
Voltage = voltageL1N,
|
||||
Phi = ATan2(reactivePowerL1, activePowerL1) // TODO: check that this works
|
||||
},
|
||||
L2 = new AcPhase
|
||||
{
|
||||
Current = currentL2,
|
||||
Voltage = voltageL2N,
|
||||
Phi = ATan2(reactivePowerL2, activePowerL2)
|
||||
},
|
||||
L3 = new AcPhase
|
||||
{
|
||||
Current = currentL3,
|
||||
Voltage = voltageL3N,
|
||||
Phi = ATan2(reactivePowerL3, activePowerL3)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,26 +1,10 @@
|
|||
using InnovEnergy.Lib.StatusApi.Connections;
|
||||
using InnovEnergy.Lib.StatusApi.Devices;
|
||||
using InnovEnergy.Lib.StatusApi;
|
||||
|
||||
namespace InnovEnergy.Lib.Devices.EmuMeter;
|
||||
|
||||
public record EmuMeterStatus
|
||||
(
|
||||
ThreePhaseAcConnection Ac,
|
||||
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)
|
||||
{}
|
||||
public record EmuMeterStatus : PowerMeterStatus
|
||||
{
|
||||
// TODO add serial nb, (and other?)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -2,8 +2,7 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using InnovEnergy.Lib.Devices.Trumpf.TruConvert;
|
||||
using InnovEnergy.Lib.Protocols.Modbus.Clients;
|
||||
using InnovEnergy.Lib.Protocols.Modbus.Connections;
|
||||
using InnovEnergy.Lib.StatusApi.Connections;
|
||||
using InnovEnergy.Lib.StatusApi.Phases;
|
||||
using InnovEnergy.Lib.Units.Composite;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
using static DecimalMath.DecimalEx;
|
||||
using static InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.AcControlRegisters;
|
||||
|
@ -216,7 +215,7 @@ public class TruConvertAcDevice
|
|||
|
||||
return new TruConvertAcStatus
|
||||
(
|
||||
Ac: new ThreePhaseAcConnection
|
||||
Ac: new Ac3Bus
|
||||
(
|
||||
new AcPhase(gridVoltageL1,phaseCurrentL1, ACos(powerAcL1/apparentPowerAcL1)),
|
||||
new AcPhase(gridVoltageL2,phaseCurrentL2, ACos(powerAcL2/apparentPowerAcL2)),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using InnovEnergy.Lib.Devices.Trumpf.TruConvert;
|
||||
using InnovEnergy.Lib.StatusApi.Connections;
|
||||
using InnovEnergy.Lib.StatusApi.Devices;
|
||||
using InnovEnergy.Lib.Units.Composite;
|
||||
|
||||
namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc;
|
||||
|
||||
|
@ -9,7 +9,7 @@ using WarningMessages = IReadOnlyList<WarningMessage>;
|
|||
|
||||
public record TruConvertAcStatus
|
||||
(
|
||||
ThreePhaseAcConnection Ac,
|
||||
Ac3Bus Ac,
|
||||
DcConnection Dc,
|
||||
String SerialNumber,
|
||||
MainState MainState,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using InnovEnergy.Lib.Devices.Trumpf.TruConvert;
|
||||
using InnovEnergy.Lib.StatusApi.Connections;
|
||||
using InnovEnergy.Lib.StatusApi.Devices;
|
||||
using InnovEnergy.Lib.StatusApi;
|
||||
using InnovEnergy.Lib.Units;
|
||||
using InnovEnergy.Lib.Units.Composite;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
|
||||
namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc;
|
||||
|
||||
|
@ -10,18 +11,22 @@ using DcCurrentLimitStates = IReadOnlyList<DcCurrentLimitState>;
|
|||
|
||||
public record TruConvertDcStatus
|
||||
(
|
||||
DcConnection Dc,
|
||||
MainState MainState,
|
||||
UInt16 NumberOfConnectedSlaves,
|
||||
UInt16 NumberOfConnectedSubSlaves,
|
||||
Decimal BatteryVoltage,
|
||||
Decimal BatteryCurrent,
|
||||
Decimal TotalDcPower,
|
||||
DcCurrentLimitStates StatusOfCurrentLimiting,
|
||||
Decimal OverloadCapacity,
|
||||
Decimal DcDcInletTemperature,
|
||||
AlarmMessages Alarms,
|
||||
WarningMessages Warnings,
|
||||
Boolean PowerOperation
|
||||
):DcDevice(Dc)
|
||||
{}
|
||||
DcBus DcLeft,
|
||||
DcBus DcRight,
|
||||
State MainState,
|
||||
Power TotalDcPower, // TODO: necessary?
|
||||
State StatusOfCurrentLimiting,
|
||||
Decimal OverloadCapacity,
|
||||
Temperature DcDcInletTemperature,
|
||||
State Alarms,
|
||||
State Warnings,
|
||||
State PowerOperation
|
||||
|
||||
// UInt16 NumberOfConnectedSlaves, // TODO: necessary?
|
||||
// UInt16 NumberOfConnectedSubSlaves, // TODO: necessary?
|
||||
) :
|
||||
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>("|");
|
||||
}
|
|
@ -10,8 +10,8 @@ using T = BatteryStatus;
|
|||
[OpParallel]
|
||||
public partial record BatteryStatus : DeviceStatus, IDcConnection
|
||||
{
|
||||
public required DcPhase Dc { get; init; }
|
||||
public required Percent Soc { get; init; }
|
||||
public required Temperature Temperature { get; init; }
|
||||
public DcBus Dc { get; init; }
|
||||
public Percent Soc { get; init; }
|
||||
public Temperature Temperature { get; init; }
|
||||
}
|
||||
|
|
@ -4,5 +4,5 @@ namespace InnovEnergy.Lib.StatusApi.Connections;
|
|||
|
||||
public interface IAc1Connection
|
||||
{
|
||||
Ac1Phase Ac { get; }
|
||||
Ac1Bus Ac { get; }
|
||||
}
|
|
@ -4,5 +4,5 @@ namespace InnovEnergy.Lib.StatusApi.Connections;
|
|||
|
||||
public interface IAc3Connection
|
||||
{
|
||||
Ac3Phase Ac { get; }
|
||||
Ac3Bus Ac { get; }
|
||||
}
|
|
@ -5,5 +5,5 @@ namespace InnovEnergy.Lib.StatusApi.Connections;
|
|||
|
||||
public interface IDcConnection
|
||||
{
|
||||
DcPhase Dc { get; }
|
||||
DcBus Dc { get; }
|
||||
}
|
|
@ -4,5 +4,5 @@ namespace InnovEnergy.Lib.StatusApi.Connections;
|
|||
|
||||
public interface IPvConnection
|
||||
{
|
||||
IReadOnlyList<DcPhase> Strings { get; }
|
||||
IReadOnlyList<DcBus> Strings { get; }
|
||||
}
|
|
@ -6,8 +6,8 @@ namespace InnovEnergy.Lib.StatusApi;
|
|||
[OpParallel]
|
||||
public partial record DcDcConverterStatus : DeviceStatus
|
||||
{
|
||||
public required DcPhase Left { get; init; }
|
||||
public required DcPhase Right { get; init; }
|
||||
public DcBus Left { get; init; }
|
||||
public DcBus Right { get; init; }
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,12 +1,64 @@
|
|||
using System.Text.Json;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
using static InnovEnergy.Lib.Units.Units;
|
||||
|
||||
namespace InnovEnergy.Lib.StatusApi;
|
||||
|
||||
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()
|
||||
.Generate(t => t.BaseType!)
|
||||
.First(t => t.IsAbstract)
|
||||
.Name
|
||||
.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();
|
||||
// }
|
||||
// }
|
|
@ -7,8 +7,8 @@ namespace InnovEnergy.Lib.StatusApi;
|
|||
[OpParallel]
|
||||
public partial record MpptStatus : IDcConnection, IPvConnection
|
||||
{
|
||||
public required DcPhase Dc { get; init; }
|
||||
public required IReadOnlyList<DcPhase> Strings { get; init; }
|
||||
public DcBus Dc { get; init; }
|
||||
public IReadOnlyList<DcBus> Strings { get; init; }
|
||||
}
|
||||
|
||||
|
|
@ -7,5 +7,5 @@ namespace InnovEnergy.Lib.StatusApi;
|
|||
[OpParallel]
|
||||
public partial record PowerMeterStatus : DeviceStatus, IAc3Connection
|
||||
{
|
||||
public required Ac3Phase Ac { get; init; }
|
||||
public Ac3Bus Ac { get; init; }
|
||||
}
|
|
@ -10,6 +10,6 @@ public partial record SinglePhaseInverterStatus :
|
|||
IAc1Connection,
|
||||
IDcConnection
|
||||
{
|
||||
public required Ac1Phase Ac { get; init; }
|
||||
public required DcPhase Dc { get; init; }
|
||||
public Ac1Bus Ac { get; init; }
|
||||
public DcBus Dc { get; init; }
|
||||
}
|
||||
|
|
|
@ -10,6 +10,6 @@ public partial record SinglePhasePvInverterStatus :
|
|||
IAc1Connection,
|
||||
IPvConnection
|
||||
{
|
||||
public required Ac1Phase Ac { get; init; }
|
||||
public required IReadOnlyList<DcPhase> Strings { get; init; }
|
||||
public Ac1Bus Ac { get; init; }
|
||||
public IReadOnlyList<DcBus> Strings { get; init; }
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../InnovEnergy.Lib.props" />
|
||||
<!-- <Import Project="../../App/InnovEnergy.App.props" />-->
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../Protocols/Modbus/Modbus.csproj" />
|
||||
|
|
|
@ -10,7 +10,7 @@ public partial record ThreePhaseInverterStatus :
|
|||
IAc3Connection,
|
||||
IDcConnection
|
||||
{
|
||||
public required Ac3Phase Ac { get; init; }
|
||||
public required DcPhase Dc { get; init; }
|
||||
public Ac3Bus Ac { get; init; }
|
||||
public DcBus Dc { get; init; }
|
||||
}
|
||||
|
|
@ -10,6 +10,6 @@ public partial record ThreePhasePvInverterStatus :
|
|||
IAc3Connection,
|
||||
IPvConnection
|
||||
{
|
||||
public required Ac3Phase Ac { get; init; }
|
||||
public required IReadOnlyList<DcPhase> Strings { get; init; }
|
||||
public Ac3Bus Ac { get; init; }
|
||||
public IReadOnlyList<DcBus> Strings { get; init; }
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
#define Sum
|
||||
|
||||
using static System.Math;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
|
||||
namespace InnovEnergy.Lib.Units;
|
||||
|
||||
|
@ -10,7 +13,7 @@ using T = Angle;
|
|||
public readonly partial struct Angle
|
||||
{
|
||||
public Decimal Value { get; }
|
||||
public override String ToString() => Value + Unit;
|
||||
public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
|
||||
|
||||
// scalar multiplication
|
||||
|
||||
|
@ -75,3 +78,19 @@ public readonly partial struct Angle
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,9 @@
|
|||
#define Sum
|
||||
|
||||
using static System.Math;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
|
||||
namespace InnovEnergy.Lib.Units;
|
||||
|
||||
|
@ -10,7 +13,7 @@ using T = ApparentPower;
|
|||
public readonly partial struct ApparentPower
|
||||
{
|
||||
public Decimal Value { get; }
|
||||
public override String ToString() => Value + Unit;
|
||||
public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
|
||||
|
||||
// scalar multiplication
|
||||
|
||||
|
@ -75,3 +78,19 @@ public readonly partial struct ApparentPower
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -1,19 +1,18 @@
|
|||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
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")]
|
||||
public static Ac1Phase operator |(Ac1Phase left, Ac1Phase right)
|
||||
public static Ac1Bus operator |(Ac1Bus left, Ac1Bus right)
|
||||
{
|
||||
var f = left.Frequency | right.Frequency;
|
||||
var p = (AcPhase)left | (AcPhase)right;
|
||||
|
||||
return new Ac1Phase
|
||||
return new Ac1Bus
|
||||
{
|
||||
Frequency = f,
|
||||
Current = p.Current,
|
||||
|
@ -22,7 +21,6 @@ public record Ac1Phase : AcPhase
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -3,20 +3,18 @@ using static DecimalMath.DecimalEx;
|
|||
|
||||
namespace InnovEnergy.Lib.Units.Composite;
|
||||
|
||||
public record Ac3Phase
|
||||
public record Ac3Bus
|
||||
{
|
||||
public required AcPhase L1 { get; init; }
|
||||
public required AcPhase L2 { get; init; }
|
||||
public required AcPhase L3 { get; init; }
|
||||
public required Frequency Frequency { get; init; }
|
||||
public AcPhase L1 { get; init; }
|
||||
public AcPhase L2 { get; init; }
|
||||
public AcPhase L3 { get; init; }
|
||||
public Frequency Frequency { get; init; }
|
||||
|
||||
public ApparentPower ApparentPower => L1.ApparentPower + L2.ApparentPower + L3.ApparentPower;
|
||||
public ReactivePower ReactivePower => L1.ReactivePower + L2.ReactivePower + L3.ReactivePower;
|
||||
public Power ActivePower => L1.ActivePower + L2.ActivePower + L3.ActivePower;
|
||||
public Angle Phi => ATan2(ReactivePower, ActivePower);
|
||||
|
||||
public static Ac3Phase operator |(Ac3Phase left, Ac3Phase right) => OpParallel(left, right);
|
||||
private static readonly Func<Ac3Phase, Ac3Phase, Ac3Phase> OpParallel = "|".CreateBinaryOpForProps<Ac3Phase>();
|
||||
|
||||
|
||||
public static Ac3Bus operator |(Ac3Bus left, Ac3Bus right) => OpParallel(left, right);
|
||||
private static readonly Func<Ac3Bus, Ac3Bus, Ac3Bus> OpParallel = "|".CreateBinaryOpForProps<Ac3Bus>();
|
||||
}
|
|
@ -3,28 +3,28 @@ using static DecimalMath.DecimalEx;
|
|||
namespace InnovEnergy.Lib.Units.Composite;
|
||||
|
||||
|
||||
public record AcPhase : IPhase
|
||||
public record AcPhase : IBus
|
||||
{
|
||||
private readonly Voltage _Voltage;
|
||||
public required Voltage Voltage
|
||||
public Voltage 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;
|
||||
public required Current Current
|
||||
public Current 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 Power ActivePower => ApparentPower.Value * PowerFactor;
|
||||
public Power ActivePower => ApparentPower.Value * PowerFactor.Value;
|
||||
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)
|
||||
|
|
|
@ -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>();
|
||||
}
|
|
@ -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>();
|
||||
|
||||
|
||||
}
|
|
@ -4,7 +4,7 @@ namespace InnovEnergy.Lib.Units.Composite;
|
|||
|
||||
[SuppressMessage("ReSharper", "MemberCanBeProtected.Global")]
|
||||
|
||||
public interface IPhase
|
||||
public interface IBus
|
||||
{
|
||||
public Voltage Voltage { get; }
|
||||
public Current Current { get; }
|
|
@ -2,6 +2,9 @@
|
|||
#define Sum
|
||||
|
||||
using static System.Math;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
|
||||
namespace InnovEnergy.Lib.Units;
|
||||
|
||||
|
@ -10,7 +13,7 @@ using T = Current;
|
|||
public readonly partial struct Current
|
||||
{
|
||||
public Decimal Value { get; }
|
||||
public override String ToString() => Value + Unit;
|
||||
public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
|
||||
|
||||
// scalar multiplication
|
||||
|
||||
|
@ -75,3 +78,19 @@ public readonly partial struct Current
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,9 @@
|
|||
#define Equal
|
||||
|
||||
using static System.Math;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
|
||||
namespace InnovEnergy.Lib.Units;
|
||||
|
||||
|
@ -10,7 +13,7 @@ using T = Frequency;
|
|||
public readonly partial struct Frequency
|
||||
{
|
||||
public Decimal Value { get; }
|
||||
public override String ToString() => Value + Unit;
|
||||
public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
|
||||
|
||||
// scalar multiplication
|
||||
|
||||
|
@ -75,3 +78,19 @@ public readonly partial struct Frequency
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source.
|
||||
#define Type
|
||||
#define AggregationType
|
||||
|
||||
using static System.Math;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
|
||||
namespace InnovEnergy.Lib.Units;
|
||||
|
||||
|
@ -10,7 +13,7 @@ using T = Template;
|
|||
public readonly partial struct Template
|
||||
{
|
||||
public Decimal Value { get; }
|
||||
public override String ToString() => Value + Unit;
|
||||
public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
|
||||
|
||||
// scalar multiplication
|
||||
|
||||
|
@ -75,3 +78,19 @@ public readonly partial struct Template
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -11,5 +11,5 @@ do
|
|||
file=$(basename -- "$path")
|
||||
class="${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
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ public readonly struct Percent
|
|||
public Percent(Decimal value) => Value = value;
|
||||
|
||||
// not generated
|
||||
// TODO: generate?
|
||||
|
||||
public Decimal Value { get; }
|
||||
public override String ToString() => Value + Unit;
|
||||
|
@ -20,4 +21,23 @@ public readonly struct Percent
|
|||
|
||||
// parallel
|
||||
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();
|
||||
}
|
|
@ -11,7 +11,6 @@ public readonly partial struct Power
|
|||
|
||||
public Power(Decimal value) => Value = value;
|
||||
|
||||
|
||||
// P=UI
|
||||
public static Voltage operator /(Power power, Current current) => new Voltage(power.Value / current.Value);
|
||||
public static Current operator /(Power power, Voltage voltage) => new Current(power.Value / voltage.Value);
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
#define Sum
|
||||
|
||||
using static System.Math;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
|
||||
namespace InnovEnergy.Lib.Units;
|
||||
|
||||
|
@ -10,7 +13,7 @@ using T = Power;
|
|||
public readonly partial struct Power
|
||||
{
|
||||
public Decimal Value { get; }
|
||||
public override String ToString() => Value + Unit;
|
||||
public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
|
||||
|
||||
// scalar multiplication
|
||||
|
||||
|
@ -75,3 +78,19 @@ public readonly partial struct Power
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,9 @@
|
|||
#define Sum
|
||||
|
||||
using static System.Math;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
|
||||
namespace InnovEnergy.Lib.Units;
|
||||
|
||||
|
@ -10,7 +13,7 @@ using T = ReactivePower;
|
|||
public readonly partial struct ReactivePower
|
||||
{
|
||||
public Decimal Value { get; }
|
||||
public override String ToString() => Value + Unit;
|
||||
public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
|
||||
|
||||
// scalar multiplication
|
||||
|
||||
|
@ -75,3 +78,19 @@ public readonly partial struct ReactivePower
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,9 @@
|
|||
#define Sum
|
||||
|
||||
using static System.Math;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
|
||||
namespace InnovEnergy.Lib.Units;
|
||||
|
||||
|
@ -10,7 +13,7 @@ using T = Resistance;
|
|||
public readonly partial struct Resistance
|
||||
{
|
||||
public Decimal Value { get; }
|
||||
public override String ToString() => Value + Unit;
|
||||
public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
|
||||
|
||||
// scalar multiplication
|
||||
|
||||
|
@ -75,3 +78,19 @@ public readonly partial struct Resistance
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
using System.Collections;
|
||||
|
||||
namespace InnovEnergy.Lib.Units;
|
||||
|
||||
public readonly struct State
|
||||
public readonly struct State : IReadOnlyList<String>
|
||||
{
|
||||
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 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(Enum e) => new State(e.ToString());
|
||||
public static implicit operator State(Boolean s) => new State(s.ToString());
|
||||
public static implicit operator State(String s) => new(s);
|
||||
public static implicit operator State(Enum e) => new(e.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);
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public Int32 Count => Values.Count;
|
||||
public String this[Int32 index] => Values[index];
|
||||
}
|
|
@ -2,6 +2,9 @@
|
|||
#define Mean
|
||||
|
||||
using static System.Math;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
|
||||
namespace InnovEnergy.Lib.Units;
|
||||
|
||||
|
@ -10,7 +13,7 @@ using T = Temperature;
|
|||
public readonly partial struct Temperature
|
||||
{
|
||||
public Decimal Value { get; }
|
||||
public override String ToString() => Value + Unit;
|
||||
public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
|
||||
|
||||
// scalar multiplication
|
||||
|
||||
|
@ -75,3 +78,19 @@ public readonly partial struct Temperature
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -1,10 +1,27 @@
|
|||
using System.Text.Json.Serialization;
|
||||
using InnovEnergy.Lib.Units.Json;
|
||||
|
||||
namespace InnovEnergy.Lib.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 static Current A (this Decimal value) => new Current(value);
|
||||
|
@ -17,13 +34,5 @@ public static class Units
|
|||
public static Angle Rad (this Decimal value) => new Angle(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
|
||||
};
|
||||
}
|
|
@ -2,6 +2,9 @@
|
|||
#define Equal
|
||||
|
||||
using static System.Math;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
|
||||
namespace InnovEnergy.Lib.Units;
|
||||
|
||||
|
@ -10,7 +13,7 @@ using T = Voltage;
|
|||
public readonly partial struct Voltage
|
||||
{
|
||||
public Decimal Value { get; }
|
||||
public override String ToString() => Value + Unit;
|
||||
public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit;
|
||||
|
||||
// scalar multiplication
|
||||
|
||||
|
@ -75,3 +78,19 @@ public readonly partial struct Voltage
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -1,32 +1,36 @@
|
|||
using DecimalMath;
|
||||
using static System.Math;
|
||||
using static DecimalMath.DecimalEx;
|
||||
|
||||
namespace InnovEnergy.Lib.Utils;
|
||||
|
||||
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)
|
||||
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 magnitude = Math.Pow(10, power);
|
||||
var shifted = Math.Round(num * magnitude);
|
||||
var shifted = Round(num * 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)
|
||||
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 magnitude = DecimalEx.Pow(10, power);
|
||||
var shifted = Math.Round(num * magnitude);
|
||||
var shifted = Round(num * magnitude);
|
||||
|
||||
return shifted / magnitude;
|
||||
}
|
||||
}
|
|
@ -106,7 +106,7 @@ public static class EnumerableUtils
|
|||
public static IEnumerable<T> NullableToEnumerable<T>(this T? t)
|
||||
{
|
||||
if (t is not null)
|
||||
yield return t!;
|
||||
yield return t;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var value = seed;
|
||||
while (true)
|
||||
while (value is not null)
|
||||
{
|
||||
yield return value;
|
||||
value = next(value);
|
||||
|
|
|
@ -2,11 +2,6 @@
|
|||
|
||||
<Import Project="../../InnovEnergy.Lib.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Reactive" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -6,11 +6,6 @@
|
|||
<DebugType>full</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../Utils/Utils.csproj" />
|
||||
<ProjectReference Include="../../Time/Time.csproj" />
|
||||
|
|
Loading…
Reference in New Issue