using System.Diagnostics.CodeAnalysis; using System.Text; 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; [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 StringActive_ BatteryStrings => new StringActive_(this); public IoStatus_ IoStatus => new IoStatus_(this); public Boolean Eoc => ParseEocReached();//Leds is { Green: On, Amber: Off, Blue : Off }; // ParseEocReached(); // public UInt16 LimpBitMap => _LimpBitMap; public String BatteryState => ParseBatteryState(); public String SerialNumber => $"{_SerialNum1:X4}{_SerialNum2:X4}{_SerialNum3:X4}{_SerialNum4:X4}".TrimEnd('0'); public String FwVersion => _FwVersion.ToString("X4"); public Strings Warnings => ParseWarnings().OrderBy(w => w).ToList(); public Strings Alarms => ParseAlarms() .OrderBy(w => w).ToList(); public Percent Soc => _Soc; public Double SOCAh => _SOCAh; public Current BusCurrent => _BusCurrent; public Current CellsCurrent => _CellsCurrent; public Current HeatingCurrent => _BusCurrent - _CellsCurrent; public DcPower HeatingPower => HeatingCurrent * Dc.Voltage; // Time since TOC is a counter from the last moment when the battery reached EOC // When The battery is full charged (reached EOC) the Time Since TOC is set to 0 public TimeSpan TimeSinceTOC => TimeSpan.FromMinutes(_TimeSinceToc); public Boolean CalibrationChargeRequested => TimeSinceTOC > TimeSpan.FromDays(7); // From AF0A (Fw Version) Each 14 days , But Max and Peter asked for 7 days. 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 StringActive_ { public Boolean String1Active => (Self._LimpBitMap & 1) == 0; public Boolean String2Active => (Self._LimpBitMap & 2) == 0; public Boolean String3Active => (Self._LimpBitMap & 4) == 0; public Boolean String4Active => (Self._LimpBitMap & 8) == 0; public Boolean String5Active => (Self._LimpBitMap & 16) == 0; internal StringActive_(Battery48TlRecord self) => Self = self; private Battery48TlRecord Self { get; } } public readonly struct IoStatus_ { public Boolean ConnectedToDcBus => ((Self._IoStates >> 0) & 1) == 0; public Boolean AlarmOutActive => ((Self._IoStates >> 1) & 1) == 0; public Boolean InternalFanActive => ((Self._IoStates >> 2) & 1) == 1; public Boolean VoltMeasurementAllowed => ((Self._IoStates >> 3) & 1) == 1; public Boolean AuxRelayBus => ((Self._IoStates >> 4) & 1) == 0; public Boolean RemoteStateActive => ((Self._IoStates >> 5) & 1) == 1; public Boolean RiscActive => ((Self._IoStates >> 6) & 1) == 1; internal IoStatus_(Battery48TlRecord self) => Self = self; private Battery48TlRecord Self { get; } } 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_ { public Temperature Center => Self._TemperaturesCellsCenter; public Temperature Left => Self._TemperaturesCellsLeft; public Temperature Right => Self._TemperaturesCellsRight; public Temperature Average => Self._TemperaturesCellsAverage; internal Cells_(Battery48TlRecord self) => Self = self; private Battery48TlRecord Self { get; } } public readonly struct Dc_ { public Voltage Voltage => Self._CellsVoltage; public Current Current => Self._CellsCurrent; public ActivePower Power => Self._CellsVoltage * Self._CellsCurrent; internal Dc_(Battery48TlRecord self) => Self = self; private Battery48TlRecord Self { get; } } private String ParseBatteryState() { var s1 = Encoding.ASCII.GetString(BitConverter.GetBytes(_BatteryState1).Reverse().ToArray()); // endian swap var s2 = Encoding.ASCII.GetString(BitConverter.GetBytes(_BatteryState2).Reverse().ToArray()); // endian swap return s1 + s2; } private Boolean ParseEocReached() { return "EOC_" == ParseBatteryState(); } [SuppressMessage("ReSharper", "StringLiteralTypo")] private IEnumerable ParseAlarms() { Boolean HasBit(Int16 bit) => (_AlarmFlags & 1uL << bit) > 0; if (HasBit(0)) yield return "Tam : Ambient Temperature too low"; if (HasBit(2)) yield return "TaM2 : Ambient 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 : Voltage measurement circuit fails"; 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(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(42)) yield return "HTFS : Heater Fuse Blown"; if (HasBit(43)) yield return "DATA : Parameters out of range"; if (HasBit(45)) yield return "LMPA : Unbalance string voltages"; if (HasBit(46)) yield return "HEBT : Loss of heartbeat"; if (HasBit(48)) yield return "CURM : Battery charge requested after EDCH"; } [SuppressMessage("ReSharper", "StringLiteralTypo")] private IEnumerable ParseWarnings() { 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(22)) yield return "vsm1: String voltage too low"; 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(33)) yield return "CCBF : Internal charger hardware failure"; if (HasBit(35)) yield return "Ah_W: String SOC low"; if (HasBit(38)) yield return "MPMM: Midpoint wiring problem"; if (HasBit(40)) yield return "TCdi: Temperature difference between strings high"; if (HasBit(44)) yield return "LMPW : String voltages unbalance warning"; if (HasBit(47)) yield return "TOCW : Top of Charge requested"; if (HasBit(49)) yield return "BUSL : Bus lower than string"; } 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); } } }