| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // This file is part of INSTINCT, the INS Toolkit for Integrated | ||
| 2 | // Navigation Concepts and Training by the Institute of Navigation of | ||
| 3 | // the University of Stuttgart, Germany. | ||
| 4 | // | ||
| 5 | // This Source Code Form is subject to the terms of the Mozilla Public | ||
| 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| 7 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
| 8 | |||
| 9 | #include "NmeaFile.hpp" | ||
| 10 | |||
| 11 | #include "util/Logger.hpp" | ||
| 12 | #include "Navigation/Transformations/CoordinateFrames.hpp" | ||
| 13 | #include "Navigation/Transformations/Units.hpp" | ||
| 14 | #include "util/Time/TimeBase.hpp" | ||
| 15 | #include "util/StringUtil.hpp" | ||
| 16 | |||
| 17 | #include "internal/FlowManager.hpp" | ||
| 18 | #include "NodeData/State/PosVel.hpp" | ||
| 19 | |||
| 20 | 115 | NAV::NmeaFile::NmeaFile() | |
| 21 |
3/6✓ Branch 1 taken 115 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 115 times.
✗ Branch 5 not taken.
✓ Branch 8 taken 115 times.
✗ Branch 9 not taken.
|
115 | : Node(typeStatic()) |
| 22 | { | ||
| 23 | LOG_TRACE("{}: called", name); | ||
| 24 | |||
| 25 | 115 | _hasConfig = true; | |
| 26 | 115 | _guiConfigDefaultWindowSize = { 517, 87 }; | |
| 27 | |||
| 28 |
4/8✓ Branch 1 taken 115 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 115 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 115 times.
✓ Branch 9 taken 115 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
|
345 | CreateOutputPin("PosVel", Pin::Type::Flow, { NAV::PosVel::type() }, &NmeaFile::pollData); |
| 29 | 230 | } | |
| 30 | |||
| 31 | 232 | NAV::NmeaFile::~NmeaFile() | |
| 32 | { | ||
| 33 | LOG_TRACE("{}: called", nameId()); | ||
| 34 | 232 | } | |
| 35 | |||
| 36 | 229 | std::string NAV::NmeaFile::typeStatic() | |
| 37 | { | ||
| 38 |
1/2✓ Branch 1 taken 229 times.
✗ Branch 2 not taken.
|
458 | return "NmeaFile"; |
| 39 | } | ||
| 40 | |||
| 41 | ✗ | std::string NAV::NmeaFile::type() const | |
| 42 | { | ||
| 43 | ✗ | return typeStatic(); | |
| 44 | } | ||
| 45 | |||
| 46 | 114 | std::string NAV::NmeaFile::category() | |
| 47 | { | ||
| 48 |
1/2✓ Branch 1 taken 114 times.
✗ Branch 2 not taken.
|
228 | return "Data Provider"; |
| 49 | } | ||
| 50 | |||
| 51 | ✗ | void NAV::NmeaFile::guiConfig() | |
| 52 | { | ||
| 53 | ✗ | if (auto res = FileReader::guiConfig(".*", { ".*" }, size_t(id), nameId())) | |
| 54 | { | ||
| 55 | ✗ | LOG_DEBUG("{}: Path changed to {}", nameId(), _path); | |
| 56 | ✗ | flow::ApplyChanges(); | |
| 57 | ✗ | if (res == FileReader::PATH_CHANGED) | |
| 58 | { | ||
| 59 | ✗ | doReinitialize(); | |
| 60 | } | ||
| 61 | else | ||
| 62 | { | ||
| 63 | ✗ | doDeinitialize(); | |
| 64 | } | ||
| 65 | } | ||
| 66 | ✗ | } | |
| 67 | |||
| 68 | ✗ | [[nodiscard]] json NAV::NmeaFile::save() const | |
| 69 | { | ||
| 70 | LOG_TRACE("{}: called", nameId()); | ||
| 71 | |||
| 72 | ✗ | json j; | |
| 73 | |||
| 74 | ✗ | j["FileReader"] = FileReader::save(); | |
| 75 | |||
| 76 | ✗ | return j; | |
| 77 | ✗ | } | |
| 78 | |||
| 79 | 1 | void NAV::NmeaFile::restore(json const& j) | |
| 80 | { | ||
| 81 | LOG_TRACE("{}: called", nameId()); | ||
| 82 | |||
| 83 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | if (j.contains("FileReader")) |
| 84 | { | ||
| 85 | 1 | FileReader::restore(j.at("FileReader")); | |
| 86 | } | ||
| 87 | 1 | } | |
| 88 | |||
| 89 | 1 | bool NAV::NmeaFile::initialize() | |
| 90 | { | ||
| 91 | LOG_TRACE("{}: called", nameId()); | ||
| 92 | |||
| 93 | 1 | return FileReader::initialize(); | |
| 94 | } | ||
| 95 | |||
| 96 | 1 | void NAV::NmeaFile::deinitialize() | |
| 97 | { | ||
| 98 | LOG_TRACE("{}: called", nameId()); | ||
| 99 | |||
| 100 | 1 | FileReader::deinitialize(); | |
| 101 | 1 | } | |
| 102 | |||
| 103 | 2 | bool NAV::NmeaFile::resetNode() | |
| 104 | { | ||
| 105 | 2 | FileReader::resetReader(); | |
| 106 | |||
| 107 | 2 | _hasValidDate = false; | |
| 108 | 2 | _oldSoD = -1.0; | |
| 109 | |||
| 110 | 2 | return true; | |
| 111 | } | ||
| 112 | |||
| 113 | 1 | bool NAV::NmeaFile::setDateFromZDA(const std::string& line) | |
| 114 | { | ||
| 115 | // decode ZDA string according to http://www.nmea.de/nmea0183datensaetze.html#zda | ||
| 116 | |||
| 117 | // 0 1 2 3 4 5 6 | ||
| 118 | // | | | | | | | | ||
| 119 | // $--ZDA,hhmmss.ss,xx,xx,xxxx,xx,xx*hh<CR><LF> | ||
| 120 |
2/4✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
1 | std::vector<std::string> splittedString = str::split(line, ","); |
| 121 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | if (splittedString.size() == 7) |
| 122 | { | ||
| 123 | 1 | std::size_t pos_star = splittedString.back().find('*'); | |
| 124 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (pos_star != std::string::npos) |
| 125 | { | ||
| 126 |
1/2✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | int64_t crc = std::strtol(splittedString[6].substr(pos_star + 1).c_str(), nullptr, 16); |
| 127 | // checksum calculation similar to https://gist.github.com/devendranaga/fce8e166f4335fa777650493cb9246db | ||
| 128 | 1 | int64_t mycrc = 0; | |
| 129 | 1 | pos_star = line.find('*'); | |
| 130 |
2/2✓ Branch 0 taken 32 times.
✓ Branch 1 taken 1 times.
|
33 | for (unsigned int i = 1; i < pos_star; i++) |
| 131 | { | ||
| 132 |
1/2✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
|
32 | mycrc ^= line.at(i); |
| 133 | } | ||
| 134 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (mycrc == crc) |
| 135 | { | ||
| 136 |
1/2✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | _currentDate.day = std::stoi(splittedString[2]); |
| 137 |
1/2✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | _currentDate.month = std::stoi(splittedString[3]); |
| 138 |
1/2✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | _currentDate.year = std::stoi(splittedString[4]); |
| 139 | |||
| 140 | 1 | _hasValidDate = true; | |
| 141 | 1 | return true; | |
| 142 | } | ||
| 143 | } | ||
| 144 | } | ||
| 145 | ✗ | return false; | |
| 146 | 1 | } | |
| 147 | |||
| 148 | 3 | bool NAV::NmeaFile::setDateFromRMC(const std::string& line) | |
| 149 | { | ||
| 150 | // decode RMC string according to http://www.nmea.de/nmea0183datensaetze.html#rmc | ||
| 151 | |||
| 152 | // 0 1 2 3 4 5 6 7 8 9 10 11 | ||
| 153 | // | | | | | | | | | | | | | ||
| 154 | // $--RMC,hhmmss.ss,A,llll.ll,a,yyyyy.yy,a,x.x,x.x,xxxx,x.x,a*hh<CR><LF> | ||
| 155 |
2/4✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
|
3 | std::vector<std::string> splittedString = str::split(line, ","); |
| 156 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | if (splittedString.size() == 12) |
| 157 | { | ||
| 158 | 3 | std::size_t pos_star = splittedString[11].find('*'); | |
| 159 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (pos_star != std::string::npos) |
| 160 | { | ||
| 161 |
1/2✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | int64_t crc = std::strtol(splittedString[11].substr(pos_star + 1).c_str(), nullptr, 16); |
| 162 | // checksum calculation similar to https://gist.github.com/devendranaga/fce8e166f4335fa777650493cb9246db | ||
| 163 | 3 | int64_t mycrc = 0; | |
| 164 | 3 | pos_star = line.find('*'); | |
| 165 |
2/2✓ Branch 0 taken 206 times.
✓ Branch 1 taken 3 times.
|
209 | for (unsigned int i = 1; i < pos_star; i++) |
| 166 | { | ||
| 167 |
1/2✓ Branch 1 taken 206 times.
✗ Branch 2 not taken.
|
206 | mycrc ^= line.at(i); |
| 168 | } | ||
| 169 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (mycrc == crc) |
| 170 | { | ||
| 171 |
2/4✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
|
3 | _currentDate.day = std::stoi(splittedString[9].substr(0, 2)); |
| 172 |
2/4✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
|
3 | _currentDate.month = std::stoi(splittedString[9].substr(2, 2)); |
| 173 |
2/4✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
|
3 | _currentDate.year = std::stoi(splittedString[9].substr(4, 2)); |
| 174 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (_currentDate.year > 60) |
| 175 | { | ||
| 176 | ✗ | _currentDate.year += 1900; | |
| 177 | } | ||
| 178 | else | ||
| 179 | { | ||
| 180 | 3 | _currentDate.year += 2000; | |
| 181 | } | ||
| 182 | |||
| 183 | 3 | _hasValidDate = true; | |
| 184 | 3 | return true; | |
| 185 | } | ||
| 186 | } | ||
| 187 | } | ||
| 188 | ✗ | return false; | |
| 189 | 3 | } | |
| 190 | |||
| 191 | 4 | std::shared_ptr<const NAV::NodeData> NAV::NmeaFile::pollData() | |
| 192 | { | ||
| 193 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | auto obs = std::make_shared<PosVel>(); |
| 194 | |||
| 195 | // Read line | ||
| 196 | 4 | std::string line; | |
| 197 | |||
| 198 | 4 | std::vector<std::string> splittedData; | |
| 199 | |||
| 200 | 4 | int hour = 0; | |
| 201 | 4 | int minute = 0; | |
| 202 | 4 | double second = 0.0; | |
| 203 | |||
| 204 | 4 | double lat_rad = 0.0; | |
| 205 | 4 | double lon_rad = 0.0; | |
| 206 | 4 | double hgt = 0.0; | |
| 207 | |||
| 208 | while (true) | ||
| 209 | { | ||
| 210 |
1/2✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
|
13 | getline(line); |
| 211 | |||
| 212 |
3/4✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 12 times.
|
13 | if (eof()) |
| 213 | { | ||
| 214 | 1 | return nullptr; // when done with file reading | |
| 215 | } | ||
| 216 | |||
| 217 | // Remove any starting non text characters | ||
| 218 |
2/4✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
|
23 | line.erase(line.begin(), std::ranges::find_if(line, [](int ch) { return std::isgraph(ch); })); |
| 219 | |||
| 220 |
2/4✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
|
12 | splittedData = str::split(line, ","); |
| 221 | |||
| 222 |
2/2✓ Branch 2 taken 10 times.
✓ Branch 3 taken 2 times.
|
12 | if (splittedData[0].starts_with("$")) |
| 223 | { | ||
| 224 |
10/14✓ Branch 0 taken 7 times.
✓ Branch 1 taken 3 times.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 7 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 4 times.
✓ Branch 10 taken 3 times.
✓ Branch 11 taken 7 times.
✓ Branch 12 taken 3 times.
✓ Branch 14 taken 4 times.
✓ Branch 15 taken 6 times.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
|
10 | if (_hasValidDate && splittedData[0].substr(3, 3) == "GGA") |
| 225 | { | ||
| 226 | // decode GGA stream according to http://www.nmea.de/nmea0183datensaetze.html#gga | ||
| 227 | |||
| 228 | // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | ||
| 229 | // | | | | | | | | | | | | | | | | ||
| 230 | // $--GGA,hhmmss.ss,llll.ll,a,yyyyy.yy,a,x,xx,x.x,x.x,M,x.x,M,x.x,xxxx*hh<CR><LF> | ||
| 231 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
|
4 | if (splittedData.size() != 15) { continue; } |
| 232 | |||
| 233 | 4 | std::size_t pos_star = splittedData[14].find('*'); | |
| 234 |
1/2✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | int64_t crc = std::strtol(splittedData[14].substr(pos_star + 1).c_str(), nullptr, 16); |
| 235 | // checksum calculation similar to https://gist.github.com/devendranaga/fce8e166f4335fa777650493cb9246db | ||
| 236 | 4 | int64_t mycrc = 0; | |
| 237 | 4 | pos_star = line.find('*'); | |
| 238 |
2/2✓ Branch 0 taken 271 times.
✓ Branch 1 taken 4 times.
|
275 | for (unsigned int i = 1; i < pos_star; i++) |
| 239 | { | ||
| 240 |
1/2✓ Branch 1 taken 271 times.
✗ Branch 2 not taken.
|
271 | mycrc ^= line.at(i); |
| 241 | } | ||
| 242 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
|
4 | if (mycrc == crc) |
| 243 | { | ||
| 244 |
2/4✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
|
3 | hour = std::stoi(splittedData[1].substr(0, 2)); |
| 245 |
2/4✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
|
3 | minute = std::stoi(splittedData[1].substr(2, 2)); |
| 246 |
2/4✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
|
3 | second = std::stod(splittedData[1].substr(4)); |
| 247 | 3 | double newSOD = hour * 24 * 60.0 + minute * 60.0 + second; | |
| 248 | |||
| 249 | // only continue if second of day > than previous one | ||
| 250 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (newSOD < _oldSoD) |
| 251 | { | ||
| 252 | ✗ | _oldSoD = newSOD; // store current second of day for next call of this routine | |
| 253 | ✗ | _hasValidDate = false; // force wait until next ZDA stream | |
| 254 | ✗ | continue; | |
| 255 | } | ||
| 256 | |||
| 257 |
2/4✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
|
3 | double lat1 = std::stod(splittedData[2].substr(0, 2)); |
| 258 |
2/4✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
|
3 | double lat2 = std::stod(splittedData[2].substr(2)); |
| 259 | |||
| 260 | 3 | lat_rad = (lat1 + lat2 / 60.0) / 180.0 * M_PI; // convert to radian | |
| 261 | |||
| 262 |
2/4✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
|
3 | if (splittedData[3] == "S") // flip sign if south latitude |
| 263 | { | ||
| 264 | ✗ | lat_rad *= -1.0; | |
| 265 | } | ||
| 266 | |||
| 267 |
2/4✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
|
3 | double lon1 = std::stoi(splittedData[4].substr(0, 3)); |
| 268 |
2/4✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
|
3 | double lon2 = std::stod(splittedData[4].substr(3)); |
| 269 | |||
| 270 | 3 | lon_rad = (lon1 + lon2 / 60.0) / 180.0 * M_PI; // convert to radian | |
| 271 | |||
| 272 |
2/4✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
|
3 | if (splittedData[5] == "W") // flip sign if west longitude |
| 273 | { | ||
| 274 | ✗ | lon_rad *= -1.0; | |
| 275 | } | ||
| 276 | |||
| 277 |
2/4✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
|
3 | hgt = std::stod(splittedData[9]) + std::stod(splittedData[11]); // ellipsoidal height = height above geoid + geoid height |
| 278 | |||
| 279 | 3 | break; | |
| 280 | } | ||
| 281 | } | ||
| 282 |
4/6✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 6 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 1 times.
✓ Branch 9 taken 5 times.
|
6 | else if (splittedData[0].substr(3, 3) == "ZDA") |
| 283 | { | ||
| 284 |
2/4✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
|
1 | if (setDateFromZDA(line)) |
| 285 | { | ||
| 286 | 1 | _oldSoD = -1; | |
| 287 | } | ||
| 288 | 1 | continue; | |
| 289 | } | ||
| 290 |
4/6✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 5 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 3 times.
✓ Branch 9 taken 2 times.
|
5 | else if (splittedData[0].substr(3, 3) == "RMC") |
| 291 | { | ||
| 292 |
2/4✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
|
3 | if (setDateFromRMC(line)) |
| 293 | { | ||
| 294 | 3 | _oldSoD = -1; | |
| 295 | } | ||
| 296 | 3 | continue; | |
| 297 | } | ||
| 298 | } | ||
| 299 | 9 | } | |
| 300 | |||
| 301 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | Eigen::Vector3d lla_pos{ lat_rad, lon_rad, hgt }; |
| 302 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | Eigen::Vector3d n_vel{ std::nan(""), std::nan(""), std::nan("") }; // GGA streams don't contain velocity, thus set this one invalid |
| 303 |
1/2✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
|
3 | obs->insTime = InsTime(_currentDate.year, _currentDate.month, _currentDate.day, |
| 304 | hour, minute, second, | ||
| 305 | 3 | UTC); // per definition, time tags in GGA NMEA streams are in UTC | |
| 306 | |||
| 307 |
1/2✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | obs->setPosition_lla(lla_pos); |
| 308 | |||
| 309 |
1/2✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | obs->setVelocity_n(n_vel); |
| 310 | |||
| 311 |
1/2✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | invokeCallbacks(OUTPUT_PORT_INDEX_NMEA_POS_OBS, obs); |
| 312 | 3 | return obs; | |
| 313 | 4 | } | |
| 314 | |||
| 315 | 1 | NAV::FileReader::FileType NAV::NmeaFile::determineFileType() | |
| 316 | { | ||
| 317 | 1 | return FileReader::FileType::ASCII; | |
| 318 | } | ||
| 319 | |||
| 320 | 1 | void NAV::NmeaFile::readHeader() | |
| 321 | { | ||
| 322 | // Empty because NMEA does not have a header | ||
| 323 | 1 | } | |
| 324 |