using System.Net; using System.Text; using InnovEnergy.App.Collector.Influx; using InnovEnergy.App.Collector.Records; using InnovEnergy.App.Collector.Utils; using InnovEnergy.Lib.Utils; using Convert = System.Convert; // NOT (YET) USED namespace InnovEnergy.App.Collector; using Data = IReadOnlyList; public static class BatteryDataParserV4 { // public static LineProtocolPayload ParseV4Datagram(IPEndPoint endPoint, Byte[] buffer) // { // var timeOfArrival = DateTime.UtcNow; // influx wants UTC // // Log.Info($"Got V4 datagram from {endPoint}"); // // using var enumerator = buffer.ParseLengthValueEncoded().GetEnumerator(); // // var protocolVersion = enumerator // .Next() // .ToArray() // .Apply(Encoding.UTF8.GetString) // .Equals(Settings.ProtocolV4); // // var nBatteries = enumerator.NextByte(); // // foreach (var _ in Enumerable.Range(0, nBatteries)) // { // ParseBattery(enumerator); // } // // // return new LineProtocolPayload(); // } private static void ParseBattery(IEnumerator> e) { // var hardwareVersion = e.NextString(); // var firmwareVersion = e.NextString(); // var bmsVersion = e.NextString(); // // var modbusData = e.Next(); // // Int32 ReadRegisterAtIndex(Int32 index) => (modbusData[index * 2] << 8) + modbusData[index * 2 + 1]; // Int32 ReadRegister(Int32 register) => ReadRegisterAtIndex(register - 999); // // Double ReadDouble(Int32 register, Double scaleFactor = 1, Double offset = 0) // { // var value = ReadRegisterAtIndex(register - 999); // // if (value > 0x8000) // value -= 0x10000; // fiamm stores their integers signed AND with sign-offset @#%^&! // // return (value + offset) * scaleFactor; // } // TODO } private static Byte NextByte(this IEnumerator> enumerator) { return enumerator.Next().Single(); } private static String NextString(this IEnumerator> enumerator) { return enumerator .Next() .ToArray() .Apply(Encoding.UTF8.GetString); } private static String ParseString(this Data data, Int32 i) => data[i].Trim(); private static UInt16 ParseUInt16(this Data data, Int32 i) => UInt16.Parse(ParseString(data, i)); private static UInt16 ParseUInt16Register(this Data data, Int32 register) { var i = register.RegToIndex(); return data.ParseUInt16(i); } private static UInt32 ParseUInt32Register(this Data data, Int32 register) { var lo = ParseUInt16Register(data, register); var hi = ParseUInt16Register(data, register + 1); return Convert.ToUInt32(lo | (hi << 16)); } private static UInt64 ParseUInt64Register (this Data data, Int32 register) { return Enumerable .Range(0, 4) .Select(i => Convert.ToUInt64(data.ParseUInt16Register(register + i)) << (i * 16)) .Aggregate(0ul, (a, b) => a + b); // Sum() does not work for UInt64 :( } private static Decimal ParseDecimalRegister(this Data data, Int32 register, Decimal scaleFactor = 1, Decimal offset = 0) { var i = register.RegToIndex(); UInt32 n = data.ParseUInt16(i); if (n >= 0x8000) n -= 0x10000; // fiamm stores their integers signed AND with sign-offset @#%^&! return (Convert.ToDecimal(n) + offset) * scaleFactor; // according fiamm doc } private static Int32 RegToIndex(this Int32 register) => register - 992; private static Leds ParseLeds(this Data data, String installation, String batteryId) { var ledBitmap = ParseUInt16Register(data, 1004); LedState Led(Int32 n) => (LedState) (ledBitmap >> (n * 2) & 0b11); return new Leds { Installation = installation, BatteryId = batteryId, Green = Led(0), Amber = Led(1), Blue = Led(2), Red = Led(3), }; } private static IoStatus ParseIoStatus(this Data data, String installation, String batteryId) { var ioStatusBitmap = data.ParseUInt16Register(1013); Boolean IoStatus(Int32 b) => (ioStatusBitmap >> b & 1) > 0; return new IoStatus { Installation = installation, BatteryId = batteryId, MainSwitchClosed = IoStatus(0), AlarmOutActive = IoStatus(1), InternalFanActive = IoStatus(2), VoltMeasurementAllowed = IoStatus(3), AuxRelay = IoStatus(4), RemoteState = IoStatus(5), HeatingOn = IoStatus(6), }; } private static Warnings ParseWarnings(this Data data, String installation, String batteryId) { var warningsBitmap = data.ParseUInt64Register(1005); Boolean Warning(Int32 b) => (warningsBitmap >> b & 1ul) > 0; return new Warnings { Installation = installation, BatteryId = batteryId, TaM1 = Warning(1), TbM1 = Warning(4), VBm1 = Warning(6), VBM1 = Warning(8), IDM1 = Warning(10), vsM1 = Warning(24), iCM1 = Warning(26), iDM1 = Warning(28), MID1 = Warning(30), BLPW = Warning(32), Ah_W = Warning(35), MPMM = Warning(38), TCMM = Warning(39), TCdi = Warning(40), LMPW = Warning(44) }; } private static Alarms ParseAlarms(this Data data, String installation, String batteryId) { var alarmsBitmap = data.ParseUInt64Register(1009); Boolean Alarm(Int32 b) => (alarmsBitmap >> b & 1ul) > 0; return new Alarms { Installation = installation, BatteryId = batteryId, Tam = Alarm(0), TaM2 = Alarm(2), Tbm = Alarm(3), TbM2 = Alarm(5), VBm2 = Alarm(7), VBM2 = Alarm(9), IDM2 = Alarm(11), ISOB = Alarm(12), MSWE = Alarm(13), FUSE = Alarm(14), HTRE = Alarm(15), TCPE = Alarm(16), CME = Alarm(18), HWFL = Alarm(19), HWEM = Alarm(20), ThM = Alarm(21), vsm1 = Alarm(22), vsm2 = Alarm(23), vsM2 = Alarm(25), iCM2 = Alarm(27), iDM2 = Alarm(29), MID2 = Alarm(31), CCBF = Alarm(33), AhFL = Alarm(34), TbCM = Alarm(36), HTFS = Alarm(42), DATA = Alarm(43), LMPA = Alarm(45), HEBT = Alarm(46), }; } private static BatteryStatus ParseBatteryStatus(this Data data, String installation, String batteryId, Decimal temperature, Warnings warnings, Alarms alarms, DateTime lastSeen, IPEndPoint endPoint) { var activeWarnings = Active(warnings); var activeAlarms = Active(alarms); return new BatteryStatus { InstallationName = installation, BatteryId = batteryId, HardwareVersion = data.ParseString(3), FirmwareVersion = data.ParseString(4), BmsVersion = data.ParseString(5), AmpereHours = data.ParseUInt16(6), Soc = data.ParseDecimalRegister(1053, 0.1m), Voltage = data.ParseDecimalRegister(999, 0.01m), Current = data.ParseDecimalRegister(1000, 0.01m, -10000m), BusVoltage = data.ParseDecimalRegister(1001, 0.01m), Temperature = temperature, RtcCounter = data.ParseUInt32Register(1050), IpAddress = endPoint.Address.ToString(), Port = endPoint.Port, // stuff below really should be done by Grafana/InfluxDb, but not possible (yet) // aka hacks to get around limitations of Grafana/InfluxDb NumberOfWarnings = activeWarnings.Count, NumberOfAlarms = activeAlarms.Count, WarningsBitmap = data.ParseUInt64Register(1005), AlarmsBitmap = data.ParseUInt64Register(1009), LastSeen = lastSeen.ToInfluxTime() }; static IReadOnlyCollection Active(BatteryRecord record) => record .GetFields() .Where(f => f.value is Boolean b && b) .Select(f => f.key) .ToList(); } private static Temperatures ParseTemperatures(this Data data, String installation, String batteryId) { return new Temperatures { Installation = installation, BatteryId = batteryId, Battery = data.ParseDecimalRegister(1003, 0.1m, -400m), Board = data.ParseDecimalRegister(1014, 0.1m, -400m), Center = data.ParseDecimalRegister(1015, 0.1m, -400m), Lateral1 = data.ParseDecimalRegister(1016, 0.1m, -400m), Lateral2 = data.ParseDecimalRegister(1017, 0.1m, -400m), CenterHeaterPwm = data.ParseDecimalRegister(1018, 0.1m), LateralHeaterPwm = data.ParseDecimalRegister(1019, 0.1m), }; } // private static LineProtocolPayload CreatePayload(params LineProtocolPoint[] points) => // CreatePayload((IEnumerable) points); // // // private static LineProtocolPayload CreatePayload(IEnumerable points) // { // var payload = new LineProtocolPayload(); // // foreach (var point in points) // payload.Add(point); // // return payload; // } private static String CheckProtocolId(String ascii) { var protocolId = ascii.Substring(0, Settings.ProtocolV3.Length); if (protocolId != Settings.ProtocolV3) throw new Exception($"Wrong protocol header: Expected '{Settings.ProtocolV3}' but got '{protocolId}'"); return protocolId; } private static IReadOnlyList ParseBatteryRecords(Data data, String installation, DateTime lastSeen, IPEndPoint endPoint) { var batteryId = data.ParseBatteryId(); var warnings = data.ParseWarnings (installation, batteryId); var alarms = data.ParseAlarms (installation, batteryId); var leds = data.ParseLeds (installation, batteryId); var temperatures = data.ParseTemperatures (installation, batteryId); var ioStatus = data.ParseIoStatus (installation, batteryId); var batteryStatus = data.ParseBatteryStatus(installation, batteryId, temperatures.Battery, warnings, alarms, lastSeen, endPoint); return new BatteryRecord[] { batteryStatus, temperatures, leds, ioStatus, warnings, alarms }; } private static String ParseProtocolVersion (this Data data) => data.ParseString(0); private static String ParseInstallationName(this Data data) => data.ParseString(1); private static String ParseBatteryId (this Data data) => data.ParseString(2); }