using Edge.Core.IndustryStandardInterface.ATG; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; namespace VeederRoot_ATG_Console.MessageEntity.Incoming { /// /// i|I20C /// public class QueryInTankMostRecentDeliveryReportResponse : IncomingMessageBase { public int? TankNumberInFunctionCode { get { if (base.FunctionCode.Item1 == MessageFormat.Display) return null; var r = int.Parse(base.FunctionCode.Item3); return r; } } public DateTime? CurrentDateAndTime { get { if (base.FunctionCode.Item1 == MessageFormat.Display) return null; return DateTime.ParseExact( Encoding.ASCII.GetString(base.DataFieldAndOptionalCheckSumAndETX.Take(10).ToArray()), "yyMMddHHmm", CultureInfo.InvariantCulture); } } public List DeliveriesForTanks { get { /* 2 layer nested structure */ // skip 10 bytes of current date and time var rawDataBody = base.DataFieldAndOptionalCheckSumAndETX.Skip(10) //and exclude tail of -> && + 4 bytes check sum + ETX <- which is total 7 bytes. .Take(base.DataFieldAndOptionalCheckSumAndETX.Count - 10 - 7); var datas = new List(); int previousTankElapsedByteCount = 0; while (true) { var buffer = rawDataBody.Skip(previousTankElapsedByteCount); if (!buffer.Any()) return datas; /* * TT - Tank Number (Decimal, 00=all) - 2 bytes * p - Product Code (one ASCII character [20h-7Eh]) - 1 bytes * dd - Number of Deliveries to follow (Decimal, 00 if no data available for this tank) - 2 bytes * * ABOVE TOTAL LEN IS fixed 5 */ //each tank section length is decided by a fixed length + variable length. var noOfDeliversToFollow = Util.ConvertHexBcdStrToBytes(buffer.Skip(3).Take(2).ToArray()).First(); int previousDeliveryElapsedByteCount = 0; if (noOfDeliversToFollow == 0) { } else { int deliveriesProcessed = 0; //var deliveriesForTank = buffer.Skip(previousTankElapsedByteCount + 5); while (true) { if (deliveriesProcessed == noOfDeliversToFollow) break; var nextDelivery = buffer.Skip(5 + previousDeliveryElapsedByteCount + 10 + 10); if (!nextDelivery.Any()) throw new ArgumentException("Empty bytes for continue parsing Delivery"); var numberOfEightCharDataFieldsValue = Util.ConvertHexBcdStrToBytes(nextDelivery.Take(2).ToArray()).First(); previousDeliveryElapsedByteCount += 20 + 2 + numberOfEightCharDataFieldsValue * 8; deliveriesProcessed++; } }; previousTankElapsedByteCount += 5 + previousDeliveryElapsedByteCount; datas.Add(new DeliveriesForTank( buffer.Take(previousDeliveryElapsedByteCount + 5).ToArray())); } } } public override string ToLogString() { return this.GetType().Name + " Deliveries: " + (DeliveriesForTanks != null && DeliveriesForTanks.Any() ? DeliveriesForTanks.Select(d => d.ToLogString()).Aggregate((acc, n) => acc + "||" + n) : ""); } public class DeliveriesForTank { private byte[] bytes; /// /// /// /// TankNumber(2 bytes) + ProductCode(1 byte) + NoOfDeliveries(2 bytes)+ 20+2+No*8 public DeliveriesForTank(byte[] bytes) { this.bytes = bytes; } public int? TankNumber { get { var r = Encoding.ASCII.GetString(this.bytes.Take(2).ToArray()); return int.Parse(r); } } public char? ProductCode { get { return Encoding.ASCII.GetString(this.bytes.Skip(2).Take(1).ToArray()).First(); } } public int? NumberOfDeliveriesToFollow { get { var nn = Util.ConvertHexBcdStrToBytes(this.bytes.Skip(3).Take(2).ToArray()).First(); return nn; } } public List Deliveries { get { if (this.NumberOfDeliveriesToFollow == 0) return null; // exclude 5 bytes of TT + p + dd. var loopParts = this.bytes.Skip(5); var datas = new List(); int previousDeliveryByteCount = 0; while (true) { var numberOfEightCharDataFieldsValue = Util.ConvertHexBcdStrToBytes( loopParts.Skip(previousDeliveryByteCount + 20).Take(2).ToArray()).First(); //if (numberOfEightCharDataFieldsValue < 1 || numberOfEightCharDataFieldsValue > 10) // throw new ArgumentException("number Of Eight Char DataFields to follow must range from 1 to 10, but now is: " + numberOfEightCharDataFieldsValue); datas.Add( new Delivery(this.TankNumber, loopParts.Skip(previousDeliveryByteCount) .Take(10 + 10 + 2 + 8 * numberOfEightCharDataFieldsValue).ToArray())); previousDeliveryByteCount += 10 + 10 + 2 + 8 * numberOfEightCharDataFieldsValue; if (previousDeliveryByteCount == loopParts.Count()) break; } return datas; } } public string ToLogString() { return "Tank with Number: " + (this.TankNumber ?? -1) + " has ProductCode: " + (this.ProductCode ?? ' ') + ", its NumberOfDeliveriesToFollow: " + (this?.NumberOfDeliveriesToFollow ?? 0) + " and details-> " + (Deliveries != null && Deliveries.Any() ? Deliveries.Select(d => d.ToFullLogString()).Aggregate((acc, n) => acc + "||" + n) : ""); } } public class Delivery { //public enum ReadingDataName //{ // StartingVolume = 1, // StartingTcVolume = 2, // StartingWater = 3, // StartingTemp = 4, // EndingVolume = 5, // EndingTcVolume = 6, // EndingWater = 7, // EndingTemp = 8, // StartingHeight = 9, // EndingHeight = 10, //} private byte[] bytes; public Delivery(int? tankNumber, byte[] bytes) { this.TankNumber = tankNumber; this.bytes = bytes; } public int? TankNumber { get; } public DateTime? StartingDateTime { get { return DateTime.ParseExact( Encoding.ASCII.GetString(this.bytes.Take(10).ToArray()), "yyMMddHHmm", CultureInfo.InvariantCulture); } } public DateTime? EndingDateTime { get { return DateTime.ParseExact( Encoding.ASCII.GetString(this.bytes.Take(10).ToArray()), "yyMMddHHmm", CultureInfo.InvariantCulture); } } public int? NumberOfEightCharDataFieldsToFollow { get { var nn = Util.ConvertHexBcdStrToBytes(this.bytes.Skip(20).Take(2).ToArray()).First(); return nn; } } /// /// Get list of DelieveryReadingDataName:Value /// public Dictionary Datas { get { var datas = new Dictionary(); for (int i = 0; i < this.NumberOfEightCharDataFieldsToFollow.Value; i++) { var asciiEncodedBytes = Util.ConvertHexBcdStrToBytes(this.bytes.Skip(20 + 2 + 8 * i).Take(8).ToArray()); var dataValue = Util.ConvertIEEEWith4BytesToDouble(asciiEncodedBytes.ToArray()); switch (i + 1) { case 1: // Starting Volume datas.Add(DeliveryReadingDataName.StartingVolume, dataValue); break; case 2: // Starting TC Volume datas.Add(DeliveryReadingDataName.StartingTcVolume, dataValue); break; case 3: // Starting Water datas.Add(DeliveryReadingDataName.StartingWater, dataValue); break; case 4: // Starting Temp datas.Add(DeliveryReadingDataName.StartingTemp, dataValue); break; case 5: // Ending Volume datas.Add(DeliveryReadingDataName.EndingVolume, dataValue); break; case 6: // Ending TC Volume datas.Add(DeliveryReadingDataName.EndingTcVolume, dataValue); break; case 7: // Ending Water datas.Add(DeliveryReadingDataName.EndingWater, dataValue); break; case 8: // Ending Temp datas.Add(DeliveryReadingDataName.EndingTemp, dataValue); break; case 9: // Starting Height datas.Add(DeliveryReadingDataName.StartingHeight, dataValue); break; case 10: // Ending Height datas.Add(DeliveryReadingDataName.EndingHeight, dataValue); break; default: // by seeing the real data from console, it may still hit here // the reason is the "Number of eight character Data Fields to follow (Hex)" > 10. // and meanwhile, extra bytes will be send in, may the VeederRoot leave it for extention // in future? break; } } return datas; } } public string ToFullLogString() { return "DeliveryDetail StartingDateTime: " + (this?.StartingDateTime?.ToString("yyMMddHHmm") ?? "") + ", EndingDateTime: " + (this?.EndingDateTime?.ToString("yyMMddHHmm") ?? "") + ", data(ReadingData:Value)-> " + this.Datas.Select(s => s.Key.ToString() + ":" + s.Value).Aggregate((acc, n) => acc + " | " + n); } //public string ToSimpleLogString() //{ // return "Tank with Number: " + (this.TankNumber ?? -1) + " has ProductCode: " + (this.ProductCode ?? ' '); //} } } }