Innovenergy_trunk/csharp/App/Collector/src/BatteryDataParser.cs

340 lines
11 KiB
C#

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 InnovEnergy.Lib.Utils.Net;
using Convert = System.Convert;
namespace InnovEnergy.App.Collector;
using Data = IReadOnlyList<String>;
public static class BatteryDataParser
{
public static IReadOnlyList<BatteryRecord> ParseDatagram(UdpDatagram datagram)
{
return ParseV3Datagram(datagram);
// if (IsV4Payload(buffer))
// return ParseV4Datagram(endPoint, buffer);
// throw new Exception($"Wrong protocol header: Expected '{Settings.ProtocolV3}'");
}
private static Boolean IsV4Payload(Byte[] buffer)
{
return buffer
.ParseLengthValueEncoded()
.First()
.ToArray()
.Apply(Encoding.UTF8.GetString)
.Equals(Settings.ProtocolV4);
}
private static Boolean IsV3Payload(IEnumerable<Byte> buffer)
{
return buffer
.ToArray(Settings.ProtocolV3.Length)
.Apply(Encoding.UTF8.GetString)
.Equals(Settings.ProtocolV3);
}
// private static LineProtocolPayload ParseV4Datagram(IPEndPoint endPoint, Byte[] buffer)
// {
// var timeOfArrival = DateTime.UtcNow; // influx wants UTC
//
// BatteryDataParserV4.ParseV4Datagram(endPoint, buffer);
// return new LineProtocolPayload();
// }
private static IReadOnlyList<BatteryRecord> ParseV3Datagram(UdpDatagram datagram)
{
var timeOfArrival = DateTime.UtcNow;
var data = datagram
.Payload
.ToArray()
.Apply(Encoding.UTF8.GetString)
.Split('\n');
data.ParseProtocolVersion().Apply(CheckProtocolId);
var installationName = data.ParseInstallationName();
return ParseBatteryRecords(data, installationName, timeOfArrival, datagram.EndPoint);
}
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(((hi << 16) | lo) & UInt32.MaxValue);
}
private static UInt64 ParseUInt64Register(this Data data, Int32 register)
{
return Enumerable
.Range(register, 4)
.Reverse()
.Select(data.ParseUInt16Register)
.Aggregate(0ul, (a, b) => a << 16 | b);
}
private static Decimal ParseDecimalRegister(this Data data,
Int32 register,
Decimal scaleFactor = 1,
Decimal offset = 0)
{
var i = register.RegToIndex();
Int32 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),
};
}
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),
};
}
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<String> 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 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<BatteryRecord> 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);
}