Line |
Branch |
Decision |
Exec |
Source |
1 |
|
|
|
/* |
2 |
|
|
|
* mlg_reader.cpp |
3 |
|
|
|
* |
4 |
|
|
|
*/ |
5 |
|
|
|
|
6 |
|
|
|
#include <iostream> |
7 |
|
|
|
#include <fstream> |
8 |
|
|
|
#include <cstdint> // For fixed-width integer types like int32_t |
9 |
|
|
|
#include <stdexcept> // For std::runtime_error |
10 |
|
|
|
#include <chrono> // For time-related operations |
11 |
|
|
|
#include <ctime> // For std::put_time and std::localtime |
12 |
|
|
|
#include "mlg_reader.h" |
13 |
|
|
|
#define FIXED_HEADER_SIZE 24 |
14 |
|
|
|
|
15 |
|
|
✗ |
int readSwappedInt(std::ifstream *ifs) { |
16 |
|
|
✗ |
int32_t value; |
17 |
|
|
✗ |
if (!ifs->read(reinterpret_cast<char*>(&value), sizeof(value))) { |
18 |
|
|
✗ |
throw std::runtime_error("Error reading value"); |
19 |
|
|
|
} |
20 |
|
|
✗ |
uint32_t swapped_value = ((value & 0x000000FF) << 24) |
21 |
|
|
✗ |
| ((value & 0x0000FF00) << 8) | ((value & 0x00FF0000) >> 8) |
22 |
|
|
✗ |
| ((value & 0xFF000000) >> 24); |
23 |
|
|
✗ |
return static_cast<int32_t>(swapped_value); |
24 |
|
|
|
} |
25 |
|
|
|
|
26 |
|
|
✗ |
float readSwappedFloat(std::ifstream *ifs) { |
27 |
|
|
✗ |
int value; |
28 |
|
|
✗ |
if (!ifs->read(reinterpret_cast<char*>(&value), sizeof(value))) { |
29 |
|
|
✗ |
throw std::runtime_error("Error reading value"); |
30 |
|
|
|
} |
31 |
|
|
|
union { |
32 |
|
|
|
float f; |
33 |
|
|
|
uint32_t i; |
34 |
|
|
|
} converter; |
35 |
|
|
✗ |
uint32_t swapped_bytes = ((value & 0x000000FF) << 24) |
36 |
|
|
✗ |
| ((value & 0x0000FF00) << 8) | ((value & 0x00FF0000) >> 8) |
37 |
|
|
✗ |
| ((value & 0xFF000000) >> 24); |
38 |
|
|
|
|
39 |
|
|
✗ |
converter.i = swapped_bytes; |
40 |
|
|
✗ |
return converter.f; |
41 |
|
|
|
} |
42 |
|
|
|
|
43 |
|
|
✗ |
int16_t readSwappedShort(std::ifstream *ifs) { |
44 |
|
|
✗ |
int16_t value; |
45 |
|
|
✗ |
if (!ifs->read(reinterpret_cast<char*>(&value), sizeof(value))) { |
46 |
|
|
✗ |
throw std::runtime_error("Error reading value"); |
47 |
|
|
|
} |
48 |
|
|
✗ |
uint16_t swapped_value = ((value & 0x00FF) << 8) | ((value & 0xFF00) >> 8); |
49 |
|
|
✗ |
return static_cast<int16_t>(swapped_value); |
50 |
|
|
|
} |
51 |
|
|
|
|
52 |
|
|
✗ |
int8_t readByte(std::ifstream *ifs) { |
53 |
|
|
✗ |
int8_t value; |
54 |
|
|
✗ |
if (!ifs->read(reinterpret_cast<char*>(&value), sizeof(value))) { |
55 |
|
|
✗ |
throw std::runtime_error("Error reading value"); |
56 |
|
|
|
} |
57 |
|
|
✗ |
return value; |
58 |
|
|
|
} |
59 |
|
|
|
|
60 |
|
|
✗ |
static std::string readFixedSizeString(std::ifstream &ifs, int size) { |
61 |
|
|
✗ |
std::string s; |
62 |
|
|
✗ |
s.reserve(size); // Pre-allocate memory |
63 |
|
|
✗ |
bool terminated = false; |
64 |
|
|
✗ |
for (int i = 0; i < size; ++i) { |
65 |
|
|
✗ |
char c; |
66 |
|
|
✗ |
if (!ifs.read(&c, 1)) { |
67 |
|
|
|
throw std::runtime_error( |
68 |
|
|
|
"Error reading fixed size string byte " |
69 |
|
|
✗ |
+ std::to_string(i)); |
70 |
|
|
|
} |
71 |
|
|
✗ |
if (c == 0) { |
72 |
|
|
✗ |
terminated = true; |
73 |
|
|
|
} |
74 |
|
|
✗ |
if (!terminated) { |
75 |
|
|
✗ |
s += c; |
76 |
|
|
|
} |
77 |
|
|
|
} |
78 |
|
|
✗ |
return s; |
79 |
|
|
✗ |
} |
80 |
|
|
|
|
81 |
|
|
✗ |
int BinarySensorReader::readRecordsMetadata(std::ifstream &ifs, |
82 |
|
|
|
int numberOfFields) { |
83 |
|
|
✗ |
int lineTotalSize = 0; |
84 |
|
|
✗ |
for (int i = 0; i < numberOfFields; ++i) { |
85 |
|
|
✗ |
int8_t typeCode = readByte(&ifs); |
86 |
|
|
✗ |
std::string fieldName = readFixedSizeString(ifs, 34); |
87 |
|
|
✗ |
std::string units = readFixedSizeString(ifs, 10); |
88 |
|
|
|
|
89 |
|
|
|
// std::cout << "fieldName [" << fieldName << "]" << std::endl; |
90 |
|
|
|
// std::cout << "units [" << units << "]" << std::endl; |
91 |
|
|
|
|
92 |
|
|
✗ |
/*int8_t style = */readByte(&ifs); |
93 |
|
|
✗ |
float scale = readSwappedFloat(&ifs); |
94 |
|
|
✗ |
float transform = readSwappedFloat(&ifs); |
95 |
|
|
✗ |
int8_t digits = readByte(&ifs); |
96 |
|
|
✗ |
/*category*/readFixedSizeString(ifs, 34); |
97 |
|
|
|
|
98 |
|
|
✗ |
MlgDataType type = findByOrdinal(typeCode); |
99 |
|
|
✗ |
lineTotalSize += getRecordSize(type); |
100 |
|
|
|
|
101 |
|
|
✗ |
Record *record = new Record(fieldName, type, scale); |
102 |
|
|
✗ |
recordByName[fieldName] = record; |
103 |
|
|
✗ |
records.emplace_back(record); |
104 |
|
|
✗ |
} |
105 |
|
|
|
|
106 |
|
|
✗ |
if (afterHeaderCallback) { |
107 |
|
|
✗ |
afterHeaderCallback(); |
108 |
|
|
|
} |
109 |
|
|
|
|
110 |
|
|
✗ |
return lineTotalSize; |
111 |
|
|
|
} |
112 |
|
|
|
|
113 |
|
|
✗ |
void BinarySensorReader::readLoggerFieldData() { |
114 |
|
|
✗ |
/*uint16_t timestamp =*/static_cast<uint16_t>(readSwappedShort(&ifs)); |
115 |
|
|
|
|
116 |
|
|
|
// std::cout << "Reading for record " << recordCounter << std::endl; |
117 |
|
|
|
|
118 |
|
|
✗ |
for (Record *record : records) { |
119 |
|
|
✗ |
float value = record->read(ifs); |
120 |
|
|
✗ |
currentSnapshot[record->getFieldName()] = value; |
121 |
|
|
|
} |
122 |
|
|
|
|
123 |
|
|
✗ |
/*uint8_t crc = */static_cast<uint8_t>(readByte(&ifs)); // Use the new helper |
124 |
|
|
|
|
125 |
|
|
✗ |
recordCounter++; |
126 |
|
|
|
//logContent.emplace_back(currentSnapshot); |
127 |
|
|
✗ |
} |
128 |
|
|
|
|
129 |
|
|
✗ |
std::map<const std::string, float>& BinarySensorReader::readBlock() { |
130 |
|
|
✗ |
uint8_t blockType = static_cast<uint8_t>(readByte(&ifs)); // Use the new helper |
131 |
|
|
✗ |
uint8_t counter = static_cast<uint8_t>(readByte(&ifs)); // Use the new helper |
132 |
|
|
|
|
133 |
|
|
✗ |
if (blockType == 0) { |
134 |
|
|
✗ |
readLoggerFieldData(); |
135 |
|
|
✗ |
} else if (blockType == 1) { |
136 |
|
|
✗ |
throw std::runtime_error("todo support markers"); |
137 |
|
|
|
} else { |
138 |
|
|
|
throw std::runtime_error( |
139 |
|
|
✗ |
"Unexpected block type " + std::to_string(blockType)); |
140 |
|
|
|
} |
141 |
|
|
|
|
142 |
|
|
✗ |
return currentSnapshot; |
143 |
|
|
|
} |
144 |
|
|
|
|
145 |
|
|
✗ |
void BinarySensorReader::openMlg(const std::string fileName) { |
146 |
|
|
✗ |
ifs.open(fileName, std::ios::binary); |
147 |
|
|
|
|
148 |
|
|
✗ |
if (!ifs.is_open()) { |
149 |
|
|
✗ |
throw std::runtime_error("Error opening file: " + fileName); |
150 |
|
|
|
} |
151 |
|
|
|
|
152 |
|
|
✗ |
int header = readSwappedInt(&ifs); |
153 |
|
|
|
|
154 |
|
|
✗ |
std::cout << "header " << std::hex << header << std::dec << std::endl; |
155 |
|
|
✗ |
if (header != 0x4d4c564c) { |
156 |
|
|
✗ |
throw std::runtime_error("header " + std::to_string(header)); |
157 |
|
|
|
} |
158 |
|
|
|
|
159 |
|
|
✗ |
int32_t version = readSwappedInt(&ifs); |
160 |
|
|
✗ |
std::cout << "version " << std::hex << version << std::dec << std::endl; |
161 |
|
|
✗ |
if (version != 0x47000002) { |
162 |
|
|
✗ |
throw std::runtime_error("version " + std::to_string(version)); |
163 |
|
|
|
} |
164 |
|
|
|
|
165 |
|
|
✗ |
int32_t timeStamp = readSwappedInt(&ifs); |
166 |
|
|
✗ |
std::cout << "timeStamp " << timeStamp; |
167 |
|
|
|
|
168 |
|
|
|
{ |
169 |
|
|
✗ |
std::time_t ts_seconds = static_cast<std::time_t>(timeStamp); |
170 |
|
|
✗ |
std::tm *ptm = std::localtime(&ts_seconds); |
171 |
|
|
✗ |
char buffer[32]; |
172 |
|
|
✗ |
std::strftime(buffer, 32, "%Y-%m-%d %H:%M:%S", ptm); |
173 |
|
|
✗ |
std::cout << " " << buffer << std::endl; |
174 |
|
|
|
} |
175 |
|
|
|
|
176 |
|
|
✗ |
int32_t infoDataState = readSwappedInt(&ifs); |
177 |
|
|
✗ |
std::cout << "infoDataState " << std::hex << infoDataState << "/" |
178 |
|
|
✗ |
<< std::dec << infoDataState << std::endl; |
179 |
|
|
|
|
180 |
|
|
✗ |
int32_t dataBeginIndex = readSwappedInt(&ifs); |
181 |
|
|
✗ |
std::cout << "dataBeginIndex " << std::hex << dataBeginIndex << "/" |
182 |
|
|
✗ |
<< std::dec << dataBeginIndex << std::endl; |
183 |
|
|
|
|
184 |
|
|
✗ |
uint16_t recordLength = readSwappedShort(&ifs); |
185 |
|
|
✗ |
uint16_t numberOfFields = readSwappedShort(&ifs); |
186 |
|
|
✗ |
std::cout << "numberOfFields=" << numberOfFields << std::endl; |
187 |
|
|
|
|
188 |
|
|
✗ |
int fieldsHeaderAreaSize = 89 * numberOfFields; |
189 |
|
|
✗ |
std::cout << "fields area size " << fieldsHeaderAreaSize |
190 |
|
|
✗ |
<< ", recordLength=" << recordLength << std::endl; |
191 |
|
|
|
|
192 |
|
|
✗ |
int infoBlockExpectedSize = dataBeginIndex - FIXED_HEADER_SIZE |
193 |
|
|
|
- fieldsHeaderAreaSize; |
194 |
|
|
✗ |
bool isInfoBlockExpected = infoBlockExpectedSize > 0; |
195 |
|
|
✗ |
if (isInfoBlockExpected) { |
196 |
|
|
✗ |
std::cout << "Expecting infoBlock " << infoBlockExpectedSize |
197 |
|
|
✗ |
<< std::endl; |
198 |
|
|
|
} |
199 |
|
|
|
|
200 |
|
|
✗ |
int lineTotalSize = readRecordsMetadata(ifs, numberOfFields); |
201 |
|
|
|
|
202 |
|
|
✗ |
if (isInfoBlockExpected) { |
203 |
|
|
|
|
204 |
|
|
✗ |
std::string infoBlock = readFixedSizeString(ifs, infoBlockExpectedSize); |
205 |
|
|
✗ |
std::cout << "Skipping infoBlock length=" << infoBlock.length() |
206 |
|
|
✗ |
<< std::endl; |
207 |
|
|
✗ |
int sizeValidation = dataBeginIndex - infoBlock.length() |
208 |
|
|
✗ |
- fieldsHeaderAreaSize - FIXED_HEADER_SIZE - 1; |
209 |
|
|
|
|
210 |
|
|
✗ |
if (sizeValidation != 0) |
211 |
|
|
|
throw std::runtime_error( |
212 |
|
|
|
"Size validation failed by " |
213 |
|
|
✗ |
+ std::to_string(sizeValidation)); |
214 |
|
|
✗ |
} |
215 |
|
|
✗ |
} |
216 |
|
|
|
|
217 |
|
|
✗ |
void BinarySensorReader::readMlg(mlg_logline_callback_t callback) { |
218 |
|
|
✗ |
while (!eof()) { |
219 |
|
|
✗ |
callback(readBlock()); |
220 |
|
|
|
} |
221 |
|
|
|
|
222 |
|
|
✗ |
std::cout << "Got " << recordCounter << " record(s)" << std::endl; |
223 |
|
|
|
|
224 |
|
|
✗ |
ifs.close(); |
225 |
|
|
✗ |
} |
226 |
|
|
|
|
227 |
|
|
✗ |
bool BinarySensorReader::eof() { |
228 |
|
|
✗ |
return (ifs.peek() == EOF); |
229 |
|
|
|
} |
230 |
|
|
|
|