| 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 |