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