2023-06-13 10:53:52 +00:00
|
|
|
using System.Diagnostics.CodeAnalysis;
|
|
|
|
using InnovEnergy.Lib.Devices.Battery48TL.DataTypes;
|
|
|
|
using InnovEnergy.Lib.Units;
|
|
|
|
using InnovEnergy.Lib.Units.Power;
|
|
|
|
using static InnovEnergy.Lib.Devices.Battery48TL.DataTypes.LedState;
|
|
|
|
|
|
|
|
namespace InnovEnergy.Lib.Devices.Battery48TL;
|
|
|
|
|
|
|
|
using Strings = IReadOnlyList<String>;
|
|
|
|
|
|
|
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
|
|
|
[SuppressMessage("ReSharper", "ConvertToAutoProperty")]
|
|
|
|
public partial class Battery48TlRecord
|
|
|
|
{
|
|
|
|
public Dc_ Dc => new Dc_(this);
|
|
|
|
public Leds_ Leds => new Leds_(this);
|
|
|
|
public Temperatures_ Temperatures => new Temperatures_(this);
|
|
|
|
|
|
|
|
public Boolean ConnectedToDcBus => (_IoStates & 1) == 0;
|
|
|
|
public Boolean Eoc => Leds is { Green: On, Amber: Off, Blue : Off };
|
|
|
|
|
|
|
|
public Strings Warnings => ParseWarnings().OrderBy(w => w).ToList();
|
|
|
|
public Strings Alarms => ParseAlarms() .OrderBy(w => w).ToList();
|
|
|
|
|
|
|
|
public Percent Soc => _Soc;
|
2023-07-03 12:21:00 +00:00
|
|
|
|
|
|
|
public Current BusCurrent => _BusCurrent;
|
|
|
|
|
|
|
|
public Current HeatingCurrent => _BusCurrent - _CellsCurrent;
|
2023-06-13 10:53:52 +00:00
|
|
|
|
|
|
|
public readonly struct Leds_
|
|
|
|
{
|
|
|
|
public LedState Blue => Self.ParseLed(LedColor.Blue);
|
|
|
|
public LedState Red => Self.ParseLed(LedColor.Red);
|
|
|
|
public LedState Green => Self.ParseLed(LedColor.Green);
|
|
|
|
public LedState Amber => Self.ParseLed(LedColor.Amber);
|
|
|
|
|
|
|
|
private Battery48TlRecord Self { get; }
|
|
|
|
internal Leds_(Battery48TlRecord self) => this.Self = self;
|
|
|
|
}
|
|
|
|
|
|
|
|
public readonly struct Temperatures_
|
|
|
|
{
|
|
|
|
public Boolean Heating => (Self._IoStates & 64) != 0;
|
|
|
|
public Temperature Board => Self._TemperaturesBoard;
|
|
|
|
public Cells_ Cells => new Cells_(Self);
|
|
|
|
|
|
|
|
public TemperatureState State => Self.Leds switch
|
|
|
|
{
|
|
|
|
{ Green: >= Blinking, Blue: >= Blinking } => TemperatureState.Cold,
|
|
|
|
_ => TemperatureState.Operation,
|
|
|
|
// TODO: overheated
|
|
|
|
};
|
|
|
|
|
|
|
|
internal Temperatures_(Battery48TlRecord self) => Self = self;
|
|
|
|
private Battery48TlRecord Self { get; }
|
|
|
|
}
|
|
|
|
|
|
|
|
public readonly struct Cells_
|
|
|
|
{
|
2023-06-22 07:50:24 +00:00
|
|
|
public Temperature Center => Self._TemperaturesCellsCenter;
|
|
|
|
public Temperature Left => Self._TemperaturesCellsLeft;
|
|
|
|
public Temperature Right => Self._TemperaturesCellsRight;
|
|
|
|
public Temperature Average => Self._TemperaturesCellsAverage;
|
|
|
|
|
2023-06-13 10:53:52 +00:00
|
|
|
internal Cells_(Battery48TlRecord self) => Self = self;
|
|
|
|
private Battery48TlRecord Self { get; }
|
|
|
|
}
|
|
|
|
|
|
|
|
public readonly struct Dc_
|
|
|
|
{
|
2023-07-03 12:21:00 +00:00
|
|
|
public Voltage Voltage => Self._CellsVoltage;
|
|
|
|
public Current Current => Self._CellsCurrent;
|
|
|
|
public ActivePower Power => Self._CellsVoltage * Self._CellsCurrent;
|
2023-06-13 10:53:52 +00:00
|
|
|
|
|
|
|
internal Dc_(Battery48TlRecord self) => Self = self;
|
|
|
|
private Battery48TlRecord Self { get; }
|
|
|
|
}
|
|
|
|
|
|
|
|
[SuppressMessage("ReSharper", "StringLiteralTypo")]
|
2023-06-22 07:50:24 +00:00
|
|
|
private IEnumerable<String> ParseAlarms()
|
2023-06-13 10:53:52 +00:00
|
|
|
{
|
|
|
|
Boolean HasBit(Int16 bit) => (_AlarmFlags & 1uL << bit) > 0;
|
|
|
|
|
|
|
|
if (HasBit(0) ) yield return "Tam : BMS temperature too low";
|
|
|
|
if (HasBit(2) ) yield return "TaM2 : BMS temperature too high";
|
|
|
|
if (HasBit(3) ) yield return "Tbm : Battery temperature too low";
|
|
|
|
if (HasBit(5) ) yield return "TbM2 : Battery temperature too high";
|
|
|
|
if (HasBit(7) ) yield return "VBm2 : Bus voltage too low";
|
|
|
|
if (HasBit(9) ) yield return "VBM2 : Bus voltage too high";
|
|
|
|
if (HasBit(11)) yield return "IDM2 : Discharge current too high";
|
|
|
|
if (HasBit(12)) yield return "ISOB : Electrical insulation failure";
|
|
|
|
if (HasBit(13)) yield return "MSWE : Main switch failure";
|
|
|
|
if (HasBit(14)) yield return "FUSE : Main fuse blown";
|
|
|
|
if (HasBit(15)) yield return "HTRE : Battery failed to warm up";
|
|
|
|
if (HasBit(16)) yield return "TCPE : Temperature sensor failure";
|
|
|
|
if (HasBit(17)) yield return "STRE :";
|
|
|
|
if (HasBit(18)) yield return "CME : Current sensor failure";
|
|
|
|
if (HasBit(19)) yield return "HWFL : BMS hardware failure";
|
|
|
|
if (HasBit(20)) yield return "HWEM : Hardware protection tripped";
|
|
|
|
if (HasBit(21)) yield return "ThM : Heatsink temperature too high";
|
|
|
|
if (HasBit(22)) yield return "vsm1 : String voltage too low";
|
|
|
|
if (HasBit(23)) yield return "vsm2 : Low string voltage failure";
|
|
|
|
if (HasBit(25)) yield return "vsM2 : String voltage too high";
|
|
|
|
if (HasBit(27)) yield return "iCM2 : Charge current too high";
|
|
|
|
if (HasBit(29)) yield return "iDM2 : Discharge current too high";
|
|
|
|
if (HasBit(31)) yield return "MID2 : String voltage unbalance too high";
|
|
|
|
if (HasBit(33)) yield return "CCBF : Internal charger hardware failure";
|
|
|
|
if (HasBit(34)) yield return "AhFL :";
|
|
|
|
if (HasBit(36)) yield return "TbCM :";
|
|
|
|
if (HasBit(37)) yield return "BRNF :";
|
|
|
|
if (HasBit(42)) yield return "HTFS : Heater Fuse Blown";
|
|
|
|
if (HasBit(43)) yield return "DATA : Parameters out of range";
|
|
|
|
if (HasBit(45)) yield return "CELL2:";
|
|
|
|
}
|
|
|
|
|
|
|
|
[SuppressMessage("ReSharper", "StringLiteralTypo")]
|
2023-06-22 07:50:24 +00:00
|
|
|
private IEnumerable<String> ParseWarnings()
|
2023-06-13 10:53:52 +00:00
|
|
|
{
|
|
|
|
Boolean HasBit(Int16 bit) => (_WarningFlags & 1uL << bit) > 0;
|
|
|
|
|
|
|
|
if (HasBit(1) ) yield return "TaM1: BMS temperature high";
|
|
|
|
if (HasBit(4) ) yield return "TbM1: Battery temperature high";
|
|
|
|
if (HasBit(6) ) yield return "VBm1: Bus voltage low";
|
|
|
|
if (HasBit(8) ) yield return "VBM1: Bus voltage high";
|
|
|
|
if (HasBit(10)) yield return "IDM1: Discharge current high";
|
|
|
|
if (HasBit(24)) yield return "vsM1: String voltage high";
|
|
|
|
if (HasBit(26)) yield return "iCM1: Charge current high";
|
|
|
|
if (HasBit(28)) yield return "iDM1: Discharge current high";
|
|
|
|
if (HasBit(30)) yield return "MID1: String voltages unbalanced";
|
|
|
|
if (HasBit(32)) yield return "BLPW: Not enough charging power on bus";
|
|
|
|
if (HasBit(35)) yield return "Ah_W: String SOC low";
|
|
|
|
if (HasBit(38)) yield return "MPMM: Midpoint wiring problem";
|
|
|
|
if (HasBit(39)) yield return "TCMM:";
|
|
|
|
if (HasBit(40)) yield return "TCdi: Temperature difference between strings high";
|
|
|
|
if (HasBit(41)) yield return "WMTO:";
|
|
|
|
if (HasBit(44)) yield return "bit44:";
|
|
|
|
if (HasBit(46)) yield return "CELL1:";
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Double CalcPowerLimitImposedByVoltageLimit(Double vLimit, Double rInt)
|
|
|
|
{
|
|
|
|
var v = Dc.Voltage;
|
|
|
|
var i = Dc.Current;
|
|
|
|
|
|
|
|
var dv = vLimit - v;
|
|
|
|
var di = dv / rInt;
|
|
|
|
|
|
|
|
return vLimit * (i + di);
|
|
|
|
}
|
|
|
|
|
|
|
|
private Double CalcPowerLimitImposedByCurrentLimit(Double iLimit, Double rInt)
|
|
|
|
{
|
|
|
|
var v = Dc.Voltage;
|
|
|
|
var i = Dc.Current;
|
|
|
|
|
|
|
|
var di = iLimit - i;
|
|
|
|
var dv = di * rInt;
|
|
|
|
|
|
|
|
return iLimit * (v + dv);
|
|
|
|
}
|
|
|
|
|
|
|
|
public DcPower MaxChargePower
|
|
|
|
{
|
|
|
|
get
|
|
|
|
{
|
|
|
|
var pLimits = new[]
|
|
|
|
{
|
|
|
|
CalcPowerLimitImposedByVoltageLimit(Constants.VMax, Constants.RIntMin),
|
|
|
|
CalcPowerLimitImposedByVoltageLimit(Constants.VMax, Constants.RIntMax),
|
|
|
|
CalcPowerLimitImposedByCurrentLimit(Constants.IMax, Constants.RIntMin),
|
|
|
|
CalcPowerLimitImposedByCurrentLimit(Constants.IMax, Constants.RIntMax)
|
|
|
|
};
|
|
|
|
|
|
|
|
var pLimit = pLimits.Min();
|
|
|
|
|
|
|
|
return Math.Max(pLimit, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public DcPower MaxDischargePower
|
|
|
|
{
|
|
|
|
get
|
|
|
|
{
|
|
|
|
var pLimits = new[]
|
|
|
|
{
|
|
|
|
CalcPowerLimitImposedByVoltageLimit(Constants.VMin, Constants.RIntMin),
|
|
|
|
CalcPowerLimitImposedByVoltageLimit(Constants.VMin, Constants.RIntMax),
|
|
|
|
CalcPowerLimitImposedByCurrentLimit(-Constants.IMax, Constants.RIntMin),
|
|
|
|
CalcPowerLimitImposedByCurrentLimit(-Constants.IMax, Constants.RIntMax),
|
|
|
|
};
|
|
|
|
|
|
|
|
var pLimit = pLimits.Max();
|
|
|
|
|
|
|
|
return Math.Min(pLimit, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|