1 // 2 // Copyright (c) 2010-2022 Antmicro 3 // 4 // This file is licensed under the MIT License. 5 // Full license text is available in 'licenses/MIT.txt'. 6 // 7 8 using System; 9 using System.IO; 10 using System.Collections.Generic; 11 using Antmicro.Renode.Time; 12 using Antmicro.Renode.Utilities; 13 14 namespace Antmicro.Renode.Peripherals.Sensors 15 { 16 public class SensorSamplesPacket<T> where T : SensorSample, new() 17 { SensorSamplesPacket(TimeInterval startTime, uint frequency)18 public SensorSamplesPacket(TimeInterval startTime, uint frequency) 19 { 20 Frequency = frequency; 21 StartTime = startTime; 22 Samples = new Queue<T>(); 23 } 24 ParseFile(string path, Func<List<decimal>, T> sampleConstructor)25 public static IList<SensorSamplesPacket<T>> ParseFile(string path, Func<List<decimal>, T> sampleConstructor) 26 { 27 SensorSamplesPacket<T> currentPacket = null; 28 var packets = new List<SensorSamplesPacket<T>>(); 29 decimal? sensitivity = null; 30 int? valueDimensions = null; 31 int? valueSize = null; 32 33 using(Stream stream = new FileStream(path, FileMode.Open, FileAccess.Read)) 34 { 35 int type; 36 while((type = stream.ReadByte()) != -1) 37 { 38 switch((DataType)type) 39 { 40 case DataType.Header: 41 var header = stream.ReadStruct<SensorSamplesPacketHeader>(); 42 sensitivity = (decimal)header.Sensitivity; 43 valueDimensions = header.ValueDimensions; 44 valueSize = header.ValueSize; 45 46 currentPacket = new SensorSamplesPacket<T>( 47 TimeInterval.FromSeconds(header.Time), 48 header.Frequency); 49 packets.Add(currentPacket); 50 break; 51 case DataType.Value: 52 if(currentPacket == null || !sensitivity.HasValue || !valueDimensions.HasValue || !valueSize.HasValue) 53 { 54 throw new Exceptions.RecoverableException($"Broken Sensor Samples file: {path}"); 55 } 56 57 try 58 { 59 var rawSample = stream.ReadBytes(valueSize.Value * valueDimensions.Value, throwIfEndOfStream: true); 60 var sampleValues = ParseSampleValues(rawSample, valueSize.Value, sensitivity.Value); 61 currentPacket.Samples.Enqueue(sampleConstructor(sampleValues)); 62 } 63 catch(EndOfStreamException) 64 { 65 throw new Exceptions.RecoverableException($"Sensor Samples file ends in the middle of a sample: {path}"); 66 } 67 break; 68 default: 69 throw new Exceptions.RecoverableException($"Invalid type: {type}"); 70 } 71 } 72 } 73 return packets; 74 } 75 76 public uint Frequency { get; } 77 public Queue<T> Samples { get; } 78 public TimeInterval StartTime { get; } 79 ParseSampleValues(byte[] rawSample, int valueSize, decimal sensitivity)80 private static List<decimal> ParseSampleValues(byte[] rawSample, int valueSize, decimal sensitivity) 81 { 82 var values = new List<decimal>(); 83 for(int i = 0; i < rawSample.Length; i += valueSize) 84 { 85 decimal rawValue; 86 switch(valueSize) 87 { 88 case 2: 89 rawValue = BitConverter.ToInt16(rawSample, i); 90 break; 91 case 4: 92 rawValue = BitConverter.ToInt32(rawSample, i); 93 break; 94 case 8: 95 rawValue = BitConverter.ToInt64(rawSample, i); 96 break; 97 default: 98 throw new Exceptions.RecoverableException($"Invalid value size: {valueSize}B"); 99 } 100 values.Add(rawValue / sensitivity); 101 } 102 return values; 103 } 104 105 private enum DataType : byte 106 { 107 Header, 108 Value, 109 } 110 } 111 112 // It was supposed to be a private 'Header' struct inside the 'SensorSamplesPacket' class. 113 // However, a non-generic 'Marshal.PtrToStructure' throws an exception if the type passed is 114 // "a generic type definition". It applies to the structures defined in generic classes too. 115 // 116 // Generic Marshal methods aren't available in .NET Framework 4.5. It can be moved to the class 117 // after upgrading Renode's .NET version and 'ReadStruct' / 'ToStruct' extensions ('Misc.cs'). 118 struct SensorSamplesPacketHeader 119 { ToStringAntmicro.Renode.Peripherals.Sensors.SensorSamplesPacketHeader120 public override string ToString() 121 { 122 return $"time: {Time}; frequency: {Frequency}; sensitivity: {Sensitivity}; value: dimensions: {ValueDimensions}, size: {ValueSize}"; 123 } 124 125 // Suppress the "CS0649: Field is never assigned to, and will always have its default value 0" warning. 126 #pragma warning disable 0649 127 public double Time; 128 public uint Frequency; 129 public byte ValueDimensions; 130 public byte ValueSize; 131 // 'Marshal.PtrToStructure' expects two empty bytes here as padding. 132 public double Sensitivity; 133 #pragma warning restore 0649 134 } 135 } 136 137