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" />
</ItemGroup>
<ItemGroup>
<None Remove="RemoteSupportConsole.csproj.DotSettings" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CliWrap" Version="3.6.0" />

View File

@ -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);
}
}

View File

@ -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)
{}

View File

@ -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()
);
}
}

View File

@ -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; }
}

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.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
);
}
}

View File

@ -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?)
}

View File

@ -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)),

View File

@ -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,

View File

@ -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>("|");
}

View File

@ -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; }
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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; }
}

View File

@ -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();
// }
// }

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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" />

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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
};
}
}

View File

@ -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>();
}

View File

@ -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)

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")]
public interface IPhase
public interface IBus
{
public Voltage Voltage { get; }
public Current Current { get; }

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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

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;
// 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();
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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];
}

View File

@ -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);
}
}

View File

@ -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
};
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);

View File

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

View File

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