0.5.0
Loading...
Searching...
No Matches
UbloxGnssOrbitCollector.cpp
Go to the documentation of this file.
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
10
11#include <algorithm>
12#include <chrono>
13#include <cstddef>
14#include <imgui.h>
15#include <imgui_internal.h>
16
17#include "util/Logger.hpp"
19
21#include <fmt/format.h>
22namespace nm = NAV::NodeManager;
24
31
42
47
49{
50 return "UbloxGnssOrbitCollector";
51}
52
54{
55 return typeStatic();
56}
57
59{
60 return "Converter";
61}
62
64{
65 bool isDisabled = ImGui::GetCurrentContext()->CurrentItemFlags & ImGuiItemFlags_Disabled;
66
67 if (isDisabled) { ImGui::EndDisabled(); }
68 if (ImGui::BeginTable(fmt::format("{} UbloxGnssOrbitCollector", size_t(id)).c_str(), 4,
69 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX))
70 {
71 ImGui::TableSetupColumn("");
72 ImGui::TableSetupColumn("Received");
73 ImGui::TableSetupColumn("Iono");
74 ImGui::TableSetupColumn("Building");
75 ImGui::TableHeadersRow();
76
77 for (uint64_t sys = 0xFF; sys < static_cast<uint64_t>(0xFF) << (7 * 8); sys = sys << 8UL)
78 {
79 ImGui::TableNextRow();
80 ImGui::TableNextColumn();
81
82 ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ImGui::GetColorU32(ImGuiCol_TableHeaderBg));
83 SatelliteSystem satSys{ SatelliteSystem_(sys) };
84 ImGui::TextUnformatted(fmt::format("{}", satSys).c_str());
85
86 ImGui::TableNextColumn();
87 size_t i = 0;
88 constexpr size_t ITEMS_PER_ROW = 4;
89 for (const auto& [satId, sat] : _gnssNavInfo.satellites())
90 {
91 if (satId.satSys != satSys) { continue; }
92 if (i % ITEMS_PER_ROW != 0)
93 {
94 ImGui::SameLine();
95 ImGui::SetCursorPosX(ImGui::GetCursorPosX() - ImGui::GetStyle().ItemSpacing.x);
96 ImGui::TextUnformatted(", ");
97 ImGui::SameLine();
98 }
99 ImGui::TextUnformatted(fmt::format("{}", satId).c_str());
100 if (ImGui::IsItemHovered())
101 {
102 ImGui::BeginTooltip();
103 for (const auto& satNavData : sat.getNavigationData())
104 {
105 ImGui::TextUnformatted(fmt::format("{}", satNavData->refTime.toYMDHMS(GPST)).c_str());
106 }
107 ImGui::EndTooltip();
108 }
109 i++;
110 }
111
112 ImGui::TableNextColumn();
113 std::string iono;
114 for (const auto& correction : _gnssNavInfo.ionosphericCorrections.data())
115 {
116 if (correction.satSys != satSys) { continue; }
117 if (!iono.empty()) { iono += ", "; }
118 iono += correction.alphaBeta == IonosphericCorrections::Alpha ? "α" : "β";
119 }
120 ImGui::TextUnformatted(iono.c_str());
121
122 ImGui::TableNextColumn();
123 for (const auto& builder : _ephemerisBuilder)
124 {
125 if (builder.satId.satSys != satSys
126 || std::ranges::any_of(_gnssNavInfo.satellites(), [&](const auto& sat) {
127 if (builder.satId != sat.first) { return false; }
128 return std::ranges::any_of(sat.second.getNavigationData(), [&](const std::shared_ptr<NAV::SatNavData>& satNavData) {
129 return satNavData->refTime == builder.navData->refTime;
130 });
131 }))
132 {
133 continue;
134 }
135
136 ImGui::TextUnformatted(fmt::format("{} ({}) frames: {}", builder.satId, builder.navData->refTime.toYMDHMS(GPST), builder.subframes).c_str());
137 }
138 }
139
140 ImGui::EndTable();
141 }
142 if (isDisabled) { ImGui::BeginDisabled(); }
143}
144
146{
147 LOG_TRACE("{}: called", nameId());
148 if (!_postProcessingLock.has_value())
149 {
151 _gnssNavInfo.reset();
152 }
153 else
154 {
155 _gnssNavInfo.reset();
156 }
157 _ephemerisBuilder.clear();
158 _lastAccessedBuilder.clear();
160
161 if (inputPins.at(INPUT_PORT_INDEX_UBLOX_OBS).isPinLinked()
163 && !_postProcessingLock.has_value())
164 {
165 LOG_TRACE("{}: Setting post-processing lock", nameId());
167 }
168
169 return true;
170}
171
172void NAV::UbloxGnssOrbitCollector::onDeleteLink([[maybe_unused]] OutputPin& startPin, [[maybe_unused]] InputPin& endPin)
173{
174 LOG_TRACE("{}: called for {} ==> {}", nameId(), size_t(startPin.id), size_t(endPin.id));
175
176 if (_postProcessingLock.has_value())
177 {
178 _postProcessingLock.reset();
179 }
180}
181
183{
184 LOG_DATA("{}: Searching for [{}] at [{}]", nameId(), satId, insTime.toYMDHMS(GPST));
185 if (IOD != 0)
186 {
187 _lastAccessedBuilder[satId] = IOD;
188 }
189
190 auto iter = std::ranges::find_if(_ephemerisBuilder, [&](const auto& builder) {
191 return builder.satId == satId && builder.navData->refTime == insTime;
192 });
193 if (iter == _ephemerisBuilder.end())
194 {
195 LOG_DATA("{}: Constructing new builder", nameId());
196
197 std::shared_ptr<SatNavData> satNavData = nullptr;
198 switch (SatelliteSystem_(satId.satSys))
199 {
200 case GPS:
201 satNavData = std::make_shared<GPSEphemeris>(insTime);
202 break;
203 case GAL:
204 satNavData = std::make_shared<GalileoEphemeris>(insTime);
205 break;
206 case GLO:
207 // satNavData = std::make_shared<GLONASSEphemeris>(insTime);
208 LOG_CRITICAL("{}: GLONASS not implemented yet.", nameId()); // TODO: Not yet supported
209 break;
210 case BDS:
211 // satNavData = std::make_shared<BDSEphemeris>(insTime);
212 LOG_CRITICAL("{}: BeiDou not implemented yet.", nameId()); // TODO: Not yet supported
213 break;
214 case QZSS:
215 LOG_CRITICAL("{}: QZSS not implemented yet.", nameId()); // TODO: Not yet supported
216 break;
217 case IRNSS:
218 LOG_CRITICAL("{}: IRNSS not implemented yet.", nameId()); // TODO: Not yet supported
219 break;
220 case SBAS:
221 LOG_CRITICAL("{}: SBAS not implemented yet.", nameId()); // TODO: Not yet supported
222 break;
223 case SatSys_None:
224 LOG_CRITICAL("{}: Satellite system cannot be none.", nameId());
225 break;
226 }
227 return _ephemerisBuilder.emplace_back(satId, satNavData);
228 }
229
230 LOG_DATA("{}: Found builder", nameId());
231 return *iter;
232}
233
234std::optional<std::reference_wrapper<NAV::UbloxGnssOrbitCollector::EphemerisBuilder>>
236{
237 LOG_DATA("{}: Searching for [{}] at Issue of Data [{}]", nameId(), satId, IOD);
238 _lastAccessedBuilder[satId] = IOD;
239
240 auto iter = std::ranges::find_if(_ephemerisBuilder, [&](const auto& builder) {
241 if (builder.satId == satId)
242 {
243 if (builder.navData->type == SatNavData::GPSEphemeris)
244 {
245 auto ephemeris = std::dynamic_pointer_cast<GPSEphemeris>(builder.navData);
246 return ephemeris && ephemeris->IODE == IOD;
247 }
248 if (builder.navData->type == SatNavData::GalileoEphemeris)
249 {
250 auto ephemeris = std::dynamic_pointer_cast<GalileoEphemeris>(builder.navData);
251 return ephemeris && ephemeris->IODnav == IOD;
252 }
253 }
254 return false;
255 });
256 if (iter != _ephemerisBuilder.end())
257 {
258 LOG_DATA("{}: Found builder", nameId());
259 return *iter;
260 }
261 LOG_DATA("{}: Could not find builder. Ignoring subframe.", nameId());
262 return std::nullopt;
263}
264
265std::optional<std::reference_wrapper<NAV::UbloxGnssOrbitCollector::EphemerisBuilder>>
267{
268 LOG_DATA("{}: Searching the last builder for [{}]", nameId(), satId);
269 if (_lastAccessedBuilder.contains(satId))
270 {
271 return getEphemerisBuilder(satId, _lastAccessedBuilder.at(satId));
272 }
273 LOG_DATA("{}: Could not find last accessed builder. Ignoring subframe.", nameId());
274 return std::nullopt;
275}
276
278{
279 [[maybe_unused]] auto ubloxObs = std::static_pointer_cast<const UbloxObs>(queue.extract_front());
280 LOG_DATA("{}: [{}] Received UbloxObs {}-{}", nameId(), ubloxObs->insTime.toYMDHMS(GPST),
281 vendor::ublox::getStringFromMsgClass(ubloxObs->msgClass),
282 vendor::ublox::getStringFromMsgId(ubloxObs->msgClass, ubloxObs->msgId));
283
284 if (ubloxObs->msgClass == ubx::UBX_CLASS_RXM)
285 {
286 if (static_cast<ubx::UbxRxmMessages>(ubloxObs->msgId) == ubx::UbxRxmMessages::UBX_RXM_SFRBX)
287 {
288 const auto& sfrbx = std::get<ubx::UbxRxmSfrbx>(ubloxObs->data);
289
290 SatelliteSystem satSys = ubx::getSatSys(sfrbx.gnssId);
291 SatId satId(satSys, sfrbx.svId);
292 LOG_DATA("{}: [{}][{}] Converting message", nameId(), ubloxObs->insTime.toYMDHMS(GPST), satId);
293
294 switch (SatelliteSystem_(satSys))
295 {
296 case GPS:
297 decryptGPS(satId, sfrbx, ubloxObs->insTime);
298 break;
299 case GAL:
300 decryptGalileo(satId, sfrbx, ubloxObs->insTime);
301 break;
302 case GLO:
303 decryptGLONASS(satId, sfrbx, ubloxObs->insTime);
304 break;
305 case BDS:
306 decryptBeiDou(satId, sfrbx, ubloxObs->insTime);
307 break;
308 case QZSS:
309 decryptQZSS(satId, sfrbx, ubloxObs->insTime);
310 break;
311 case IRNSS:
312 decryptIRNSS(satId, sfrbx, ubloxObs->insTime);
313 break;
314 case SBAS:
315 decryptSBAS(satId, sfrbx, ubloxObs->insTime);
316 break;
317 case SatSys_None:
318 LOG_CRITICAL("{}: Satellite system cannot be none.", nameId());
319 break;
320 }
321 }
322 }
323
324 if (_postProcessingLock.has_value()
325 && inputPins.at(INPUT_PORT_INDEX_UBLOX_OBS).isPinLinked()
326 && inputPins.at(INPUT_PORT_INDEX_UBLOX_OBS).link.getConnectedPin()->noMoreDataAvailable)
327 {
328 LOG_TRACE("{}: Post-processing lock cleared as all data read.", nameId());
329 _postProcessingLock.reset();
330 }
331}
332
333void NAV::UbloxGnssOrbitCollector::decryptGPS(const SatId& satId, const ubx::UbxRxmSfrbx& sfrbx, const InsTime& insTime)
334{
335 // u-blox 8 / u-blox M8: Receiver description - Including protocol specification, ch. 10.2, p. 30f
336 // > For GPS L1C/A signals, there is a fairly straightforward mapping between the reported subframe
337 // > and the structure of subframe and words described in the GPS ICD. Each subframe comprises ten
338 // > data words, which are reported in the same order they are received.
339 // >
340 // > MSB LSB
341 // > 1 to 10: Pad (2 bits) Data (24 bits) Parity (6 bits)
342 // >
343 // > Note that as the GPS data words only comprise 30 bits, the 2 most significant bits in each word
344 // > reported by UBX-RXM-SFRBX are padding and should be ignored.
345
346 if (sfrbx.numWords != 10)
347 {
348 LOG_ERROR("{}: [{}] Received {} instead of 10 words", nameId(), satId, sfrbx.numWords);
349 return;
350 }
351
352 size_t w = 0; // Word counter
353
354 // Telemetry Word
355 // 30 bits long, occurs every 6 seconds ┌ Integrity Status Flag
356 // Preamble │ ┌ Reserved
357 // 1 0 0 0 1 0 1 1 MSB TLM Message LSB │ │ Parity
358 // ┌──────┴──────┐ ┌───────────────┴──────────────────────┐ │ │ ┌──────┴────────┐
359 // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
360 LOG_DATA("{}: [{}] tlm: {} {}", nameId(), satId, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
361 constexpr uint8_t TLM_PREAMBLE = 0b10001011;
362 if (static_cast<uint8_t>((sfrbx.dwrd.at(w) >> 22) & 0b11111111) != TLM_PREAMBLE) // Preamble found after the 2 padding bits
363 {
364 LOG_DATA("{}: [{}] Wrong telemetry word preamble. Ignoring SFRBX message.", nameId(), satId);
365 return;
366 }
367
368 // Handover Word (HOW)
369 // 30 bits long, occurs every 6 seconds anti-spoof (A-S) flag
370 // alert flag │
371 // MSB TOW-Count Message LSB │ │ Subframe ID Parity
372 // ┌──────────────────┴────────────────────┐ │ │ ┌───┴──┐ ┌──────┴────────┐
373 // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
374 w++;
375 LOG_DATA("{}: [{}] how: {} {}", nameId(), satId, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
376 auto subFrameId = static_cast<uint8_t>((sfrbx.dwrd.at(w) >> 8) & 0b111);
377 LOG_DATA("{}: [{}] subFrameId: {}", nameId(), satId, subFrameId);
378
379 if (subFrameId == 0 || subFrameId > 5)
380 {
381 LOG_ERROR("{}: [{}] The subFrameId has to be in the range [1..=5] but it is {}", nameId(), satId, subFrameId);
382 return;
383 }
384
385 auto gpsWeekToW = insTime.toGPSweekTow(GPST);
386
387 auto finishSubFrame = [&]<size_t N>(const std::shared_ptr<SatNavData>& ephemeris, size_t subFrameId, std::bitset<N>& subframesFound) {
388 if (1 <= subFrameId && subFrameId <= 3)
389 {
390 subframesFound.set(subFrameId - 1);
391
392 LOG_DATA("{}: [{}] subframesFound: {}", nameId(), satId, subframesFound);
393 if (subframesFound.count() == 3)
394 {
395 LOG_DATA("{}: [{}] [{}] All subframes found. Updating gnnsNavInfo", nameId(), satId, ephemeris->refTime.toYMDHMS(GPST));
396 if (!_gnssNavInfo.searchNavigationData(satId, ephemeris->refTime))
397 {
398 LOG_DEBUG("{}: Adding new ephemeris data [{}] [{}]", nameId(), satId, ephemeris->refTime.toYMDHMS(GPST));
399 }
400 std::unique_lock guard(outputPins.at(OUTPUT_PORT_INDEX_GNSS_NAV_INFO).dataAccessMutex, std::defer_lock);
401 if (!_postProcessingLock.has_value())
402 {
403 guard.lock();
404 }
405 _gnssNavInfo.satelliteSystems |= satId.satSys;
406 _gnssNavInfo.addSatelliteNavData(satId, ephemeris);
407 }
408 }
409 };
410
411 if (subFrameId == 1) // Third through tenth words of subframe 1 shall each contain 6 parity bits as their LSBs
412 {
413 // IS-GPS-200M: Figure 20-1. Data Format (sheet 1 of 11), p. 79
414 // IS-GPS-200M: 20.3.3.3 Subframe 1, p. 91ff
415 // IS-GPS-200M: Table 20-I. Subframe 1 Parameters, p. 96
416
417 // T_GD, af2, af1, af0 shall be two's complement, with the sign bit (+ or -) occupying the MSB
418
419 w++;
420 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 3 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
421 [[maybe_unused]] auto WN = static_cast<uint16_t>((sfrbx.dwrd.at(w) >> 20) & 0b1111111111); // 10 BITS - Transmission Week Number
422 auto L2ChannelCodes = static_cast<uint8_t>((sfrbx.dwrd.at(w) >> 18) & 0b11); // 2 BITS
423 auto uraIndex = static_cast<uint8_t>((sfrbx.dwrd.at(w) >> 14) & 0b1111); // 4 BITS
424 auto svHealth = static_cast<uint8_t>((sfrbx.dwrd.at(w) >> 8) & 0b111111); // 6 BITS
425 auto IODC = static_cast<uint16_t>(((sfrbx.dwrd.at(w) >> 6) & 0b11) << 8); // 2 MSBs - 10 BITS TOTAL
426 LOG_DATA("{}: [{}] WN {}, L2ChannelCodes {}, uraIndex {}, svHealth {}, IODC {}", nameId(), satId, WN, L2ChannelCodes, uraIndex, svHealth, std::bitset<10>(IODC));
427
428 w++;
429 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 4 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
430 auto L2DataFlagPCode = static_cast<uint8_t>((sfrbx.dwrd.at(w) >> 29) & 0b1); // 1 BIT
431 LOG_DATA("{}: [{}] L2DataFlagPCode {}", nameId(), satId, L2DataFlagPCode);
432
433 w++;
434 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 5 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
435
436 w++;
437 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 6 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
438
439 w++;
440 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 7 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
441 auto T_GD = static_cast<int8_t>((sfrbx.dwrd.at(w) >> 6) & 0b11111111);
442 LOG_DATA("{}: [{}] T_GD {}", nameId(), satId, T_GD);
443
444 w++;
445 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 8 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
446 IODC |= static_cast<uint16_t>((sfrbx.dwrd.at(w) >> 22) & 0b11111111); // 8 LSBs - 10 BITS TOTAL
447 auto toc = static_cast<uint16_t>((sfrbx.dwrd.at(w) >> 6) & 0b1111111111111111); // 16 LSBs
448 LOG_DATA("{}: [{}] IODC {} ({}), toc {}", nameId(), satId, IODC, std::bitset<10>(IODC), toc);
449
450 w++;
451 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 9 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
452 auto af2 = static_cast<int8_t>((sfrbx.dwrd.at(w) >> 22) & 0b11111111); // 8 BITS
453 auto af1 = static_cast<int16_t>((sfrbx.dwrd.at(w) >> 6) & 0b1111111111111111); // 16 BITS
454 LOG_DATA("{}: [{}] af2 {}, af1 {}", nameId(), satId, af2, af1);
455
456 w++;
457 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 10 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
458 auto af0 = math::interpretAs<int32_t, 22>(sfrbx.dwrd.at(w) >> 8); // 22 BITS
459 LOG_DATA("{}: [{}] af0 {}", nameId(), satId, af0);
460
461 InsTime insTimeToc(gpsWeekToW.gpsCycle, gpsWeekToW.gpsWeek, toc * std::pow(2, 4), GPST);
462
463 auto& ephemerisBuilder = getEphemerisBuilder(satId, insTimeToc);
464 auto ephemeris = std::dynamic_pointer_cast<GPSEphemeris>(ephemerisBuilder.navData);
465 ephemeris->toc = insTimeToc;
466
467 ephemeris->L2ChannelCodes = L2ChannelCodes;
468 ephemeris->svAccuracy = gpsUraIdx2Val(uraIndex);
469 ephemeris->svHealth = svHealth;
470 ephemeris->IODC = IODC;
471 ephemeris->L2DataFlagPCode = L2DataFlagPCode;
472 ephemeris->T_GD = T_GD * std::pow(2, -31);
473 ephemeris->refTime = ephemeris->toc;
474 ephemeris->a = {
475 af0 * std::pow(2, -31),
476 af1 * std::pow(2, -43),
477 af2 * std::pow(2, -55),
478 };
479 LOG_DATA("{}: [{}] svAccuracy [{} m], svHealth [{}], IODC [{}], tgd [{:.3e} s]", nameId(), satId,
480 ephemeris->svAccuracy, ephemeris->svHealth, ephemeris->IODC, ephemeris->T_GD);
481 LOG_DATA("{}: [{}] toc [{}], a0 [{:.3e} s], a1 [{:.3e} s/s], a2 [{:.3e} s/s^2]", nameId(), satId,
482 ephemeris->toc.toYMDHMS(GPST), ephemeris->a[0], ephemeris->a[1], ephemeris->a[2]);
483
484 finishSubFrame(ephemeris, subFrameId, ephemerisBuilder.subframes);
485 }
486 else if (subFrameId == 2) // The third through tenth words of subframes 2 and 3 shall each contain six parity bits as their LSBs
487 {
488 // IS-GPS-200M: Figure 20-1. Data Format (sheet 2 of 11), p. 80
489 // IS-GPS-200M: 20.3.3.4 Subframes 2 and 3, p. 101ff
490 // IS-GPS-200M: Table 20-III. Ephemeris Parameters, p. 105
491
492 // Crs, delta_n, M_0, Cuc, Cus shall be two's complement, with the sign bit (+ or -) occupying the MSB
493
494 w++;
495 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 3 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
496 auto IODE = static_cast<uint8_t>((sfrbx.dwrd.at(w) >> 22) & 0b11111111); // 8 BITS
497 auto Crs = static_cast<int16_t>((sfrbx.dwrd.at(w) >> 6) & 0b1111111111111111); // 16 BITS
498 LOG_DATA("{}: [{}] IODE {}, Crs {}", nameId(), satId, IODE, Crs);
499
500 w++;
501 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 4 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
502 auto delta_n = static_cast<int16_t>((sfrbx.dwrd.at(w) >> 14) & 0b1111111111111111); // 16 BITS
503 auto M_0 = static_cast<int32_t>(((sfrbx.dwrd.at(w) >> 6) & 0b11111111) << 24); // 8 MSBs - 32 BITS TOTAL
504 LOG_DATA("{}: [{}] delta_n {}, M_0 {}", nameId(), satId, delta_n, std::bitset<32>(static_cast<uint32_t>(M_0)));
505
506 w++;
507 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 5 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
508 M_0 |= static_cast<int32_t>((sfrbx.dwrd.at(w) >> 6) & 0b111111111111111111111111); // 24 LSBs - 32 BITS TOTAL
509 LOG_DATA("{}: [{}] M_0 {} ({})", nameId(), satId, M_0, std::bitset<32>(static_cast<uint32_t>(M_0)));
510
511 w++;
512 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 6 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
513 auto Cuc = static_cast<int16_t>((sfrbx.dwrd.at(w) >> 14) & 0b1111111111111111); // 16 BITS
514 uint32_t e = ((sfrbx.dwrd.at(w) >> 6) & 0b11111111) << 24; // 8 MSBs - 32 BITS TOTAL
515 LOG_DATA("{}: [{}] Cuc {}, e {}", nameId(), satId, Cuc, std::bitset<32>(e));
516
517 w++;
518 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 7 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
519 e |= (sfrbx.dwrd.at(w) >> 6) & 0b111111111111111111111111; // 24 LSBs - 32 BITS TOTAL
520 LOG_DATA("{}: [{}] e {} ({})", nameId(), satId, e, std::bitset<32>(e));
521
522 w++;
523 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 8 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
524 auto Cus = static_cast<int16_t>((sfrbx.dwrd.at(w) >> 14) & 0b1111111111111111); // 16 BITS
525 uint32_t sqrt_A = ((sfrbx.dwrd.at(w) >> 6) & 0b11111111) << 24; // 8 MSBs - 32 BITS TOTAL
526 LOG_DATA("{}: [{}] Cus {}, sqrt_A {}", nameId(), satId, Cus, std::bitset<32>(sqrt_A));
527
528 w++;
529 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 9 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
530 sqrt_A |= (sfrbx.dwrd.at(w) >> 6) & 0b111111111111111111111111; // 24 LSBs - 32 BITS TOTAL
531 LOG_DATA("{}: [{}] sqrt_A {} ({})", nameId(), satId, sqrt_A, std::bitset<32>(sqrt_A));
532
533 w++;
534 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 10 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
535 auto toe = static_cast<uint16_t>((sfrbx.dwrd.at(w) >> 14) & 0b1111111111111111); // 16 BITS
536 auto fitInterval = static_cast<bool>((sfrbx.dwrd.at(w) >> 13) & 0b1); // 1 BIT
537 [[maybe_unused]] auto AODO = static_cast<uint8_t>((sfrbx.dwrd.at(w) >> 8) & 0b11111); // 5 BITS
538 LOG_DATA("{}: [{}] toe {}, fitInterval {}, AODO {}", nameId(), satId, toe, fitInterval, AODO);
539
540 InsTime insTimeToe(gpsWeekToW.gpsCycle, gpsWeekToW.gpsWeek, toe * std::pow(2, 4), GPST);
541
542 auto& ephemerisBuilder = getEphemerisBuilder(satId, insTimeToe);
543 auto ephemeris = std::dynamic_pointer_cast<GPSEphemeris>(ephemerisBuilder.navData);
544 ephemeris->toe = insTimeToe;
545
546 ephemeris->IODE = IODE;
547 ephemeris->Crs = Crs * std::pow(2, -5);
548 ephemeris->delta_n = semicircles2rad(delta_n * std::pow(2, -43));
549 ephemeris->M_0 = semicircles2rad(M_0 * std::pow(2, -31));
550 ephemeris->Cuc = Cuc * std::pow(2, -29);
551 ephemeris->e = e * std::pow(2, -33);
552 ephemeris->Cus = Cus * std::pow(2, -29);
553 ephemeris->sqrt_A = sqrt_A * std::pow(2, -19);
554 ephemeris->toe = InsTime(gpsWeekToW.gpsCycle, gpsWeekToW.gpsWeek, toe * std::pow(2, 4), GPST);
555 ephemeris->fitInterval = fitInterval ? 8.0 : 4.0;
556 // AODO
557
558 LOG_DATA("{}: [{}] IODE [{}], Crs [{:.3e} m], delta_n [{:.3e} rad/s], M_0 [{:.3e} rad], Cuc [{:.3e} rad]", nameId(), satId,
559 ephemeris->IODE, ephemeris->Crs, ephemeris->delta_n, ephemeris->M_0, ephemeris->Cuc);
560 LOG_DATA("{}: [{}] e [{:.3e}], Cus [{:.3e} rad], sqrt_A [{:.3e} m^(1/2)], toe [{}], fitInterval [{:.1f}]", nameId(), satId,
561 ephemeris->e, ephemeris->Cus, ephemeris->sqrt_A, ephemeris->toe.toGPSweekTow(GPST), ephemeris->fitInterval);
562
563 finishSubFrame(ephemeris, subFrameId, ephemerisBuilder.subframes);
564 }
565 else if (subFrameId == 3) // The third through tenth words of subframes 2 and 3 shall each contain six parity bits as their LSBs
566 {
567 // IS-GPS-200M: Figure 20-1. Data Format (sheet 3 of 11), p. 81
568 // IS-GPS-200M: 20.3.3.4 Subframes 2 and 3, p. 101ff
569 // IS-GPS-200M: Table 20-III. Ephemeris Parameters, p. 105
570
571 // Cic, Omega_0, Cis, i_0, Crc, omega, Omega_dot, IDOT shall be two's complement, with the sign bit (+ or -) occupying the MSB
572
573 w++;
574 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 3 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
575 auto Cic = static_cast<int16_t>((sfrbx.dwrd.at(w) >> 14) & 0b1111111111111111); // 16 BITS
576 auto Omega_0 = static_cast<int32_t>(((sfrbx.dwrd.at(w) >> 6) & 0b11111111) << 24); // 8 MSBs - 32 BITS TOTAL
577 LOG_DATA("{}: [{}] Cic {}, Omega_0 {}", nameId(), satId, Cic, std::bitset<32>(static_cast<uint32_t>(Omega_0)));
578
579 w++;
580 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 4 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
581 Omega_0 |= static_cast<int32_t>((sfrbx.dwrd.at(w) >> 6) & 0b111111111111111111111111); // 24 LSBs - 32 BITS TOTAL
582 LOG_DATA("{}: [{}] Omega_0 {} ({})", nameId(), satId, Omega_0, std::bitset<32>(static_cast<uint32_t>(Omega_0)));
583
584 w++;
585 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 5 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
586 auto Cis = static_cast<int16_t>((sfrbx.dwrd.at(w) >> 14) & 0b1111111111111111); // 16 BITS
587 auto i_0 = static_cast<int32_t>(((sfrbx.dwrd.at(w) >> 6) & 0b11111111) << 24); // 8 MSBs - 32 BITS TOTAL
588 LOG_DATA("{}: [{}] Cis {}, i_0 {}", nameId(), satId, Cis, std::bitset<32>(static_cast<uint32_t>(i_0)));
589
590 w++;
591 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 6 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
592 i_0 |= static_cast<int32_t>((sfrbx.dwrd.at(w) >> 6) & 0b111111111111111111111111); // 24 LSBs - 32 BITS TOTAL
593 LOG_DATA("{}: [{}] i_0 {} ({})", nameId(), satId, i_0, std::bitset<32>(static_cast<uint32_t>(i_0)));
594
595 w++;
596 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 7 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
597 auto Crc = static_cast<int16_t>((sfrbx.dwrd.at(w) >> 14) & 0b1111111111111111); // 16 BITS
598 auto omega = static_cast<int32_t>(((sfrbx.dwrd.at(w) >> 6) & 0b11111111) << 24); // 8 MSBs - 32 BITS TOTAL
599 LOG_DATA("{}: [{}] Crc {}, omega {}", nameId(), satId, Crc, std::bitset<32>(static_cast<uint32_t>(omega)));
600
601 w++;
602 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 8 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
603 omega |= static_cast<int32_t>((sfrbx.dwrd.at(w) >> 6) & 0b111111111111111111111111); // 24 LSBs - 32 BITS TOTAL
604 LOG_DATA("{}: [{}] omega {} ({})", nameId(), satId, omega, std::bitset<32>(static_cast<uint32_t>(omega)));
605
606 w++;
607 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 9 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
608 auto Omega_dot = math::interpretAs<int32_t, 24>(sfrbx.dwrd.at(w) >> 6); // 24 BITS
609 LOG_DATA("{}: [{}] Omega_dot {} ({})", nameId(), satId, Omega_dot, std::bitset<32>(static_cast<uint32_t>(Omega_dot)));
610
611 w++;
612 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 10 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
613 auto IODE = static_cast<uint8_t>((sfrbx.dwrd.at(w) >> 22) & 0b11111111); // 8 BITS
614 auto i_dot = math::interpretAs<int16_t, 14>(sfrbx.dwrd.at(w) >> 8); // 14 BITS
615 LOG_DATA("{}: [{}] IODE {}, i_dot {} ({})", nameId(), satId, IODE, i_dot, std::bitset<16>(static_cast<uint16_t>(i_dot)));
616
617 auto ephemerisBuilder = getEphemerisBuilder(satId, IODE);
618 if (!ephemerisBuilder.has_value())
619 {
620 LOG_DATA("{}: [{}] Could not find Ephemeris builder for IODE {}", nameId(), satId, IODE);
621 return;
622 }
623 auto ephemeris = std::dynamic_pointer_cast<GPSEphemeris>(ephemerisBuilder->get().navData);
624
625 ephemeris->Cic = Cic * std::pow(2, -29);
626 ephemeris->Omega_0 = semicircles2rad(Omega_0 * std::pow(2, -31));
627 ephemeris->Cis = Cis * std::pow(2, -29);
628 ephemeris->i_0 = semicircles2rad(i_0 * std::pow(2, -31));
629 ephemeris->Crc = Crc * std::pow(2, -5);
630 ephemeris->omega = semicircles2rad(omega * std::pow(2, -31));
631 ephemeris->Omega_dot = semicircles2rad(Omega_dot * std::pow(2, -43));
632 ephemeris->IODE = IODE;
633 ephemeris->i_dot = semicircles2rad(i_dot * std::pow(2, -43));
634
635 LOG_DATA("{}: [{}] Cic [{:.3e} rad], Omega_0 [{:.3e} rad], Cis [{:.3e} rad], i_0 [{:.3e} rad], Crc [{:.3e} m]", nameId(), satId,
636 ephemeris->Cic, ephemeris->Omega_0, ephemeris->Cis, ephemeris->i_0, ephemeris->Crc);
637 LOG_DATA("{}: [{}] omega [{:.3e} rad], Omega_dot [{:.3e} rad/s], IODE [{}], i_dot [{:.3e} rad/s]", nameId(), satId,
638 ephemeris->omega, ephemeris->Omega_dot, ephemeris->IODE, ephemeris->i_dot);
639
640 finishSubFrame(ephemeris, subFrameId, ephemerisBuilder->get().subframes);
641 }
642 else if (subFrameId == 4) // Words three through ten of each page contain six parity bits as their LSBs
643 {
644 // Page | SV ID | Description
645 // ------------------------ | ------------------------------ | -----------
646 // 1, 6, 11, 16, 21: | 57 | Reserved
647 // 2, 3, 4, 5, 7, 8, 9, 10: | 25, 26, 27, 28, 29, 30, 31, 32 | almanac data for SV 25 through 32 respectively
648 // 12, 19, 20, 22, 23, 24: | 62, 58, 59, 60, 61, 62 | Reserved
649 // 13: | 52 | NMCT
650 // 14, 15: | 53, 54 | Reserved for system use
651 // 17: | 55 | Special messages
652 // 18: | 56 | Ionospheric and UTC data
653 // 25: | 63 | A-S flags/SV configurations for 32 SVs, plus SV health for SV 25 through 32
654 //
655 // IS-GPS-200M: Table 20-V. Data IDs and SV IDs in Subframes 4 and 5, p. 114
656 // IS-GPS-200M: 20.3.3.5 Subframes 4 and 5, p. 112ff
657
658 w++;
659 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 3 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
660 [[maybe_unused]] auto dataId = static_cast<uint8_t>((sfrbx.dwrd.at(w) >> 28) & 0b11); // 2 BITS
661 auto svId = static_cast<uint8_t>((sfrbx.dwrd.at(w) >> 22) & 0b111111); // 6 BITS
662 LOG_DATA("{}: [{}] dataId {}, svId {}", nameId(), satId, dataId, svId);
663
664 if (svId == 56) // IS-GPS-200M: Figure 20-1. Data Format (sheet 8 of 11), p. 86 - for page 18
665 {
666 // IS-GPS-200M: 20.3.3.5.1.6 Coordinated Universal Time (UTC) Parameters, p. 121
667 // IS-GPS-200M: Table 20-IX. UTC Parameters, p. 122
668
669 // IS-GPS-200M: 20.3.3.5.1.7 Ionospheric Data, p. 121
670 // IS-GPS-200M: Table 20-X. Ionospheric Parameters, p. 123
671
672 auto alpha0 = static_cast<int8_t>((sfrbx.dwrd.at(w) >> 14) & 0b11111111); // 8 BITS
673 auto alpha1 = static_cast<int8_t>((sfrbx.dwrd.at(w) >> 6) & 0b11111111); // 8 BITS
674 LOG_DATA("{}: [{}] alpha0 {}, alpha1 {}", nameId(), satId, alpha0, alpha1);
675
676 w++;
677 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 4 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
678 auto alpha2 = static_cast<int8_t>((sfrbx.dwrd.at(w) >> 22) & 0b11111111); // 8 BITS
679 auto alpha3 = static_cast<int8_t>((sfrbx.dwrd.at(w) >> 14) & 0b11111111); // 8 BITS
680 auto beta0 = static_cast<int8_t>((sfrbx.dwrd.at(w) >> 6) & 0b11111111); // 8 BITS
681 LOG_DATA("{}: [{}] alpha2 {}, alpha3 {}, beta0 {}", nameId(), satId, alpha2, alpha3, beta0);
682
683 w++;
684 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 5 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
685 auto beta1 = static_cast<int8_t>((sfrbx.dwrd.at(w) >> 22) & 0b11111111); // 8 BITS
686 auto beta2 = static_cast<int8_t>((sfrbx.dwrd.at(w) >> 14) & 0b11111111); // 8 BITS
687 auto beta3 = static_cast<int8_t>((sfrbx.dwrd.at(w) >> 6) & 0b11111111); // 8 BITS
688 LOG_DATA("{}: [{}] beta1 {}, beta2 {}, beta3 {}", nameId(), satId, beta1, beta2, beta3);
689
690 w++;
691 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 6 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
692 auto A1 = math::interpretAs<int32_t, 24>(sfrbx.dwrd.at(w) >> 6); // 24 BITS
693 LOG_DATA("{}: [{}] A1 {}", nameId(), satId, A1);
694
695 w++;
696 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 7 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
697 auto A0 = static_cast<int32_t>(((sfrbx.dwrd.at(w) >> 6) & 0b111111111111111111111111) << 8); // 24 MSBs - 32 BITS TOTAL
698 LOG_DATA("{}: [{}] A0 {}", nameId(), satId, std::bitset<32>(static_cast<uint32_t>(A0)));
699
700 w++;
701 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 8 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
702 A0 |= static_cast<int32_t>((sfrbx.dwrd.at(w) >> 22) & 0b11111111); // 8 LSBs - 32 BITS TOTAL
703 LOG_DATA("{}: [{}] A0 {} ({})", nameId(), satId, A0, std::bitset<32>(static_cast<uint32_t>(A0)));
704
705 w++;
706 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 9 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
707
708 w++;
709 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 10 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
710
711 std::unique_lock guard(outputPins.at(OUTPUT_PORT_INDEX_GNSS_NAV_INFO).dataAccessMutex, std::defer_lock);
712 if (!_postProcessingLock.has_value())
713 {
714 guard.lock();
715 }
716 if (!_gnssNavInfo.ionosphericCorrections.contains(satId.satSys, IonosphericCorrections::Alpha))
717 {
718 if (auto ephemerisBuilder = getLastEphemerisBuilder(satId);
719 ephemerisBuilder.has_value())
720 {
721 auto ephemeris = std::dynamic_pointer_cast<GPSEphemeris>(ephemerisBuilder->get().navData);
722 LOG_DEBUG("{}: [{}] Received Ionospheric and Time system corrections [{}]", nameId(), satId.satSys, ephemeris->refTime.toYMDHMS(GPST));
723 }
724 else
725 {
726 LOG_DEBUG("{}: [{}] Received Ionospheric and Time system corrections", nameId(), satId.satSys);
727 }
728 }
729
730 _gnssNavInfo.ionosphericCorrections.insert(satId.satSys, IonosphericCorrections::Alpha,
731 {
732 alpha0 * std::pow(2, -30) /* [s] */,
733 alpha1 * std::pow(2, -27) /* [s/semi-circle] */,
734 alpha2 * std::pow(2, -24) /* [s/semi-circle^2] */,
735 alpha3 * std::pow(2, -24) /* [s/semi-circle^3] */,
736 });
737 _gnssNavInfo.ionosphericCorrections.insert(satId.satSys, IonosphericCorrections::Beta,
738 {
739 beta0 * std::pow(2, 11) /* [s] */,
740 beta1 * std::pow(2, 14) /* [s/semi-circle] */,
741 beta2 * std::pow(2, 16) /* [s/semi-circle^2] */,
742 beta3 * std::pow(2, 16) /* [s/semi-circle^3] */,
743 });
745 .a0 = A0 * std::pow(2, -30),
746 .a1 = A1 * std::pow(2, -50),
747 };
748 }
749 else
750 {
751 // IS-GPS-200M: Figure 20-1. Data Format (sheet 6 of 11), p. 84 - for pages 1, 6, 11, 16 and 21
752 // IS-GPS-200M: Figure 20-1. Data Format (sheet 7 of 11), p. 85 - for pages 12, 19, 20, 22, 23 and 24
753 // IS-GPS-200M: Figure 20-1. Data Format (sheet 9 of 11), p. 87 - for page 25
754 // IS-GPS-200M: Figure 20-1. Data Format (sheet 10 of 11), p. 88 - for page 13
755 // IS-GPS-200M: Figure 20-1. Data Format (sheet 11 of 11), p. 89 - for pages 14, 15 and 17
756
757 w++;
758 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 4 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
759 w++;
760 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 5 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
761 w++;
762 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 6 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
763 w++;
764 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 7 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
765 w++;
766 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 8 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
767 w++;
768 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 9 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
769 w++;
770 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 10 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
771 }
772 }
773 else if (subFrameId == 5) // Words three through ten of each page contain six parity bits as their LSBs
774 {
775 // Page description
776 // 1 through 24: almanac data for SV 1 through 24
777 // 25: SV health data for SV 1 through 24, the almanac reference time, the almanac reference week number
778 //
779 // IS-GPS-200M: Figure 20-1. Data Format (sheet 4 of 11), p. 82 - for page 1 through 24
780 // IS-GPS-200M: Figure 20-1. Data Format (sheet 5 of 11), p. 83 - for page 25
781 // IS-GPS-200M: 20.3.3.5 Subframes 4 and 5, p. 112ff
782
783 w++;
784 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 3 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
785 w++;
786 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 4 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
787 w++;
788 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 5 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
789 w++;
790 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 6 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
791 w++;
792 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 7 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
793 w++;
794 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 8 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
795 w++;
796 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 9 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
797 w++;
798 LOG_DATA("{}: [{}] word {:2}: {} {}", nameId(), satId, w + 1 /* 10 */, std::bitset<2>(sfrbx.dwrd.at(w) >> 30), std::bitset<30>(sfrbx.dwrd.at(w)));
799 }
800}
801
802void NAV::UbloxGnssOrbitCollector::decryptGalileo(const SatId& satId, const ubx::UbxRxmSfrbx& sfrbx, const InsTime& insTime)
803{
804 // u-blox 8 / u-blox M8: Receiver description - Including protocol specification, ch. 10.5, p. 32f
805 // > The Galileo E1OS and E5b signals both transmit the I/NAV message but in different configurations.
806 // > For Galileo E1OS signals, each reported subframe contains a pair of I/NAV pages as described in
807 // > the Galileo ICD.
808 // > Galileo pages can either be "Nominal" or "Alert" pages. For Nominal pages the eight words are
809 // > arranged as follows:
810 // >
811 // > MSB LSB
812 // > 1: Even/Odd (1 bit) Page type (1 bit) Word type (6 bits) Data (122 - 99) (24 bits)
813 // > 2: Data (98 - 67) (32 bits)
814 // > 3: Data (66 - 35) (32 bits)
815 // > 4: Data (34 - 17) (18 bits) Tail (6 bits) Pad (8 bits)
816 // > 5: Even/Odd (1 bit) Page type (1 bit) Data (16 - 1) (16 bits) Reserved 1 (40 - 27) (14 bits)
817 // > 6: Reserved 2 (26 - 1) (26 bits) SAR (22 - 17) (6 bits)
818 // > 7: SAR (16 - 1) (16 bits) Spare (2 bits) CRC (24 - 11) (14 bits)
819 // > 8: CRC (10 - 1) (10 bits) Reserved 2 (8 bits) Tail (6 bits) Pad (8 bits)
820 // >
821 // > Alert pages are reported in very similar manner, but the page type bits will have value 1 and the
822 // > structure of the eight words will be slightly different (as indicated by the Galileo ICD).
823
824 if (sfrbx.numWords != 8)
825 {
826 LOG_ERROR("{}: [{}] Received {} instead of 8 words", nameId(), satId, sfrbx.numWords);
827 return;
828 }
829
830 size_t w = 0; // Word counter
831
832 auto even = static_cast<uint8_t>((sfrbx.dwrd.at(w) >> 31) & 0b1);
833 auto pageTypeEven = static_cast<uint8_t>((sfrbx.dwrd.at(w) >> 30) & 0b1);
834 auto odd = static_cast<uint8_t>((sfrbx.dwrd.at(4) >> 31) & 0b1);
835 auto pageTypeOdd = static_cast<uint8_t>((sfrbx.dwrd.at(4) >> 30) & 0b1);
836 auto wordType = static_cast<uint8_t>((sfrbx.dwrd.at(w) >> 24) & 0b111111);
837 LOG_DATA("{}: [{}] wordType: {:2}, even {} ({} pageType), odd {} ({} pageType)", nameId(), satId, wordType, even, pageTypeEven, odd, pageTypeOdd);
838
839 if (even != 0 || pageTypeEven == 1
840 || odd != 1 || pageTypeOdd == 1)
841 {
842 LOG_DATA("{}: [{}] Ignoring message, because one of the page types is alert page", nameId(), satId);
843 return;
844 }
845
846 auto gpsWeekToW = insTime.toGPSweekTow(GST);
847
848 auto finishWord = [&]<size_t N>(const std::shared_ptr<SatNavData>& ephemeris, size_t wordType, std::bitset<N>& subframesFound) {
849 if (1 <= wordType && wordType <= 5)
850 {
851 subframesFound.set(wordType - 1);
852
853 LOG_DATA("{}: [{}] subframesFound: {}", nameId(), satId, subframesFound);
854 if (subframesFound.count() == 5)
855 {
856 LOG_DATA("{}: [{}] [{}] All words found. Updating gnnsNavInfo", nameId(), satId, ephemeris->refTime.toYMDHMS(GPST));
857 if (!_gnssNavInfo.searchNavigationData(satId, ephemeris->refTime))
858 {
859 LOG_DEBUG("{}: Adding new ephemeris data [{}] [{}]", nameId(), satId, ephemeris->refTime.toYMDHMS(GPST));
860 }
861 std::unique_lock guard(outputPins.at(OUTPUT_PORT_INDEX_GNSS_NAV_INFO).dataAccessMutex, std::defer_lock);
862 if (!_postProcessingLock.has_value())
863 {
864 guard.lock();
865 }
866 _gnssNavInfo.satelliteSystems |= satId.satSys;
867 _gnssNavInfo.addSatelliteNavData(satId, ephemeris);
868 }
869 }
870 };
871
872 if (wordType == 1) // Ephemeris (1/4)
873 {
874 // Galileo-OS-SIS-ICD-v2.0: ch. 4.3.5, Table 40 Bits Allocation for I/NAV Word Type 1, p. 37
875 //
876 // Even/Odd PageType WordType IODnav toe M_0 e sqrt_A Tail Pad Even/Odd PageType sqrt_A Reserved Reserved 1
877 // 1 1 6 10 14 32 32 18 6 8 1 1 14 2 14
878 // └───────────────────┬───────────────────┘ │ │ └───────┬───────┘ └───────────────────────┬──────────────────────┘
879 // Ublox 1 2 3 4 5
880
881 // Galileo-OS-SIS-ICD-v2.0: ch. 5.1.1, Table 60 Ephemeris Parameters, p. 43f
882 // > M_0 shall be two's complement, with the sign bit (+ or -) occupying the MSB
883
884 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 1 */, std::bitset<32>(sfrbx.dwrd.at(w)));
885 auto IODnav = static_cast<uint16_t>((sfrbx.dwrd.at(w) >> 14) & 0b1111111111);
886 auto toe = static_cast<uint16_t>(sfrbx.dwrd.at(w) & 0b11111111111111);
887 LOG_DATA("{}: [{}] IODnav {}, toe {}", nameId(), satId, IODnav, toe);
888
889 w++;
890 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 2 */, std::bitset<32>(sfrbx.dwrd.at(w)));
891 auto M_0 = static_cast<int32_t>(sfrbx.dwrd.at(w));
892 LOG_DATA("{}: [{}] M_0 {}", nameId(), satId, M_0);
893
894 w++;
895 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 3 */, std::bitset<32>(sfrbx.dwrd.at(w)));
896 uint32_t e = sfrbx.dwrd.at(w);
897 LOG_DATA("{}: [{}] e {}", nameId(), satId, e);
898
899 w++;
900 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 4 */, std::bitset<32>(sfrbx.dwrd.at(w)));
901 uint32_t sqrt_A = ((sfrbx.dwrd.at(w) >> 14) & 0b111111111111111111) << 14;
902 LOG_DATA("{}: [{}] sqrt_A {}", nameId(), satId, std::bitset<32>(sqrt_A));
903
904 w++;
905 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 5 */, std::bitset<32>(sfrbx.dwrd.at(w)));
906 sqrt_A |= (sfrbx.dwrd.at(w) >> 16) & 0b11111111111111;
907 LOG_DATA("{}: [{}] sqrt_A {} ({})", nameId(), satId, sqrt_A, std::bitset<32>(sqrt_A));
908
909 InsTime insTimeToe(gpsWeekToW.gpsCycle, gpsWeekToW.gpsWeek, toe * 60.0, GST);
910
911 auto& ephemerisBuilder = getEphemerisBuilder(satId, insTimeToe, IODnav);
912 auto ephemeris = std::dynamic_pointer_cast<GalileoEphemeris>(ephemerisBuilder.navData);
913 ephemeris->toe = insTimeToe;
914
915 ephemeris->IODnav = IODnav;
916 ephemeris->M_0 = semicircles2rad(M_0 * std::pow(2.0, -31));
917 ephemeris->e = e * std::pow(2.0, -33);
918 ephemeris->sqrt_A = sqrt_A * std::pow(2.0, -19);
919
920 LOG_DATA("{}: [{}] IODnav [{}], toe [{}], M_0 [{:.3e} rad], e [{:.3e}], sqrt_A [{:.3e} m^(1/2)]", nameId(), satId,
921 ephemeris->IODnav, ephemeris->toe.toYMDHMS(GPST), ephemeris->M_0, ephemeris->e, ephemeris->sqrt_A);
922
923 finishWord(ephemeris, wordType, ephemerisBuilder.subframes);
924 }
925 else if (wordType == 2) // Ephemeris (2/4)
926 {
927 // Galileo-OS-SIS-ICD-v2.0: ch. 4.3.5, Table 41 Bits Allocation for I/NAV Word Type 2, p. 37
928 //
929 // Even/Odd PageType WordType IODnav Omega_0 Omega_0 i_0 i_0 omega omega Tail Pad Even/Odd PageType i_dot Reserved Reserved 1
930 // 1 1 6 10 14 18 14 18 14 18 6 8 1 1 14 2 14
931 // └──────────────────────┬────────────────────┘ └────┬─────┘ └────┬───┘ └───────┬────────┘ └──────────────────────┬──────────────────────┘
932 // Ublox 1 2 3 4 5
933
934 // Galileo-OS-SIS-ICD-v2.0: ch. 5.1.1, Table 60 Ephemeris Parameters, p. 43f
935 // > Omega_0, i_0, omega, i_dot shall be two's complement, with the sign bit (+ or -) occupying the MSB
936
937 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 1 */, std::bitset<32>(sfrbx.dwrd.at(w)));
938 auto IODnav = static_cast<uint16_t>((sfrbx.dwrd.at(w) >> 14) & 0b1111111111);
939 auto Omega_0 = static_cast<int32_t>((sfrbx.dwrd.at(w) & 0b11111111111111) << 18);
940 LOG_DATA("{}: [{}] IODnav {}, Omega_0 {}", nameId(), satId, IODnav, std::bitset<32>(static_cast<uint32_t>(Omega_0)));
941
942 w++;
943 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 2 */, std::bitset<32>(sfrbx.dwrd.at(w)));
944 Omega_0 |= static_cast<int32_t>((sfrbx.dwrd.at(w) >> 14) & 0b111111111111111111);
945 auto i_0 = static_cast<int32_t>((sfrbx.dwrd.at(w) & 0b11111111111111) << 18);
946 LOG_DATA("{}: [{}] Omega_0 {} ({}), i_0 {}", nameId(), satId, Omega_0, std::bitset<32>(static_cast<uint32_t>(Omega_0)), std::bitset<32>(static_cast<uint32_t>(i_0)));
947
948 w++;
949 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 3 */, std::bitset<32>(sfrbx.dwrd.at(w)));
950 i_0 |= static_cast<int32_t>((sfrbx.dwrd.at(w) >> 14) & 0b111111111111111111);
951 auto omega = static_cast<int32_t>((sfrbx.dwrd.at(w) & 0b11111111111111) << 18);
952 LOG_DATA("{}: [{}] i_0 {} ({}), omega {}", nameId(), satId, i_0, std::bitset<32>(static_cast<uint32_t>(i_0)), std::bitset<32>(static_cast<uint32_t>(omega)));
953
954 w++;
955 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 4 */, std::bitset<32>(sfrbx.dwrd.at(w)));
956 omega |= static_cast<int32_t>((sfrbx.dwrd.at(w) >> 14) & 0b111111111111111111);
957 LOG_DATA("{}: [{}] omega {} ({})", nameId(), satId, omega, std::bitset<32>(static_cast<uint32_t>(omega)));
958
959 w++;
960 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 5 */, std::bitset<32>(sfrbx.dwrd.at(w)));
961 auto i_dot = math::interpretAs<int16_t, 14>(sfrbx.dwrd.at(w) >> 16);
962 LOG_DATA("{}: [{}] i_dot {} ({})", nameId(), satId, i_dot, std::bitset<16>(static_cast<uint16_t>(i_dot)));
963
964 auto ephemerisBuilder = getEphemerisBuilder(satId, IODnav);
965 if (!ephemerisBuilder.has_value())
966 {
967 LOG_DATA("{}: [{}] Could not find Ephemeris builder for IODnav {}", nameId(), satId, IODnav);
968 return;
969 }
970 auto ephemeris = std::dynamic_pointer_cast<GalileoEphemeris>(ephemerisBuilder->get().navData);
971
972 ephemeris->Omega_0 = semicircles2rad(Omega_0 * std::pow(2.0, -31));
973 ephemeris->i_0 = semicircles2rad(i_0 * std::pow(2.0, -31));
974 ephemeris->omega = semicircles2rad(omega * std::pow(2.0, -31));
975 ephemeris->i_dot = semicircles2rad(i_dot * std::pow(2.0, -43));
976
977 LOG_DATA("{}: [{}] IODnav [{}], Omega_0 [{} rad], i_0 [{:.3e} rad], omega [{:.3e} rad], i_dot [{:.3e} rad/s]", nameId(), satId,
978 ephemeris->IODnav, ephemeris->Omega_0, ephemeris->i_0, ephemeris->omega, ephemeris->i_dot);
979
980 finishWord(ephemeris, wordType, ephemerisBuilder->get().subframes);
981 }
982 else if (wordType == 3) // Ephemeris (3/4) and SISA
983 {
984 // Galileo-OS-SIS-ICD-v2.0: ch. 4.3.5, Table 42 Bits Allocation for I/NAV Word Type 3, p. 38
985 //
986 // Even/Odd PageType WordType IODnav Omega_dot Omega_dot delta_n Cuc Cuc Cus Crc Crc Crs Tail Pad Even/Odd PageType Crs SISA(E1,E5b) Reserved 1
987 // 1 1 6 10 14 10 16 6 10 16 6 10 8 6 8 1 1 8 8 14
988 // └───────────────────────┬─────────────────────┘ └──────────┬──────────┘ └─────┬─────┘ └────────┬────────┘ └───────────────────────┬───────────────────────┘
989 // Ublox 1 2 3 4 5
990
991 // Galileo-OS-SIS-ICD-v2.0: ch. 5.1.1, Table 60 Ephemeris Parameters, p. 43f
992 // > Omega_dot, delta_n, Cuc, Cus, Crc, Crs shall be two's complement, with the sign bit (+ or -) occupying the MSB
993
994 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 1 */, std::bitset<32>(sfrbx.dwrd.at(w)));
995 auto IODnav = static_cast<uint16_t>((sfrbx.dwrd.at(w) >> 14) & 0b1111111111);
996 auto Omega_dot = static_cast<int32_t>((sfrbx.dwrd.at(w) & 0b11111111111111) << 10);
997 LOG_DATA("{}: [{}] IODnav {}, Omega_dot {}", nameId(), satId, IODnav, std::bitset<32>(static_cast<uint32_t>(Omega_dot)));
998
999 w++;
1000 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 2 */, std::bitset<32>(sfrbx.dwrd.at(w)));
1001 Omega_dot |= static_cast<int32_t>((sfrbx.dwrd.at(w) >> 22) & 0b1111111111);
1002 Omega_dot = math::interpretAs<int32_t, 24>(Omega_dot);
1003 auto delta_n = static_cast<int16_t>((sfrbx.dwrd.at(w) >> 6) & 0b1111111111111111);
1004 auto Cuc = static_cast<int16_t>((sfrbx.dwrd.at(w) & 0b111111) << 10);
1005 LOG_DATA("{}: [{}] Omega_dot {} ({}), delta_n {}, Cuc {}", nameId(), satId,
1006 Omega_dot, std::bitset<32>(static_cast<uint32_t>(Omega_dot)), delta_n, std::bitset<16>(static_cast<uint16_t>(Cuc)));
1007
1008 w++;
1009 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 3 */, std::bitset<32>(sfrbx.dwrd.at(w)));
1010 Cuc |= static_cast<int16_t>((sfrbx.dwrd.at(w) >> 22) & 0b1111111111); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
1011 auto Cus = static_cast<int16_t>((sfrbx.dwrd.at(w) >> 6) & 0b1111111111111111);
1012 auto Crc = static_cast<int16_t>((sfrbx.dwrd.at(w) & 0b111111) << 10);
1013 LOG_DATA("{}: [{}] Cuc {} ({}), Cus {}, Crc {}", nameId(), satId, Cuc, std::bitset<16>(static_cast<uint16_t>(Cuc)), Cus, std::bitset<16>(static_cast<uint16_t>(Crc)));
1014
1015 w++;
1016 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 4 */, std::bitset<32>(sfrbx.dwrd.at(w)));
1017 Crc |= static_cast<int16_t>((sfrbx.dwrd.at(w) >> 22) & 0b1111111111); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
1018 auto Crs = static_cast<int16_t>(((sfrbx.dwrd.at(w) >> 14) & 0b11111111) << 8);
1019 LOG_DATA("{}: [{}] Crc {} ({}), Crs {}", nameId(), satId, Crc, std::bitset<16>(static_cast<uint16_t>(Crc)), std::bitset<16>(static_cast<uint16_t>(Crs)));
1020
1021 w++;
1022 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 5 */, std::bitset<32>(sfrbx.dwrd.at(w)));
1023 Crs |= static_cast<int16_t>((sfrbx.dwrd.at(w) >> 22) & 0b11111111); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
1024 auto SISA = static_cast<uint8_t>((sfrbx.dwrd.at(w) >> 14) & 0b11111111);
1025 LOG_DATA("{}: [{}] Crs {} ({}), SISA {}", nameId(), satId, Crs, std::bitset<16>(static_cast<uint16_t>(Crs)), SISA);
1026
1027 auto ephemerisBuilder = getEphemerisBuilder(satId, IODnav);
1028 if (!ephemerisBuilder.has_value())
1029 {
1030 LOG_DATA("{}: [{}] Could not find Ephemeris builder for IODnav {}", nameId(), satId, IODnav);
1031 return;
1032 }
1033 auto ephemeris = std::dynamic_pointer_cast<GalileoEphemeris>(ephemerisBuilder->get().navData);
1034
1035 ephemeris->Omega_dot = semicircles2rad(Omega_dot * std::pow(2.0, -43));
1036 ephemeris->delta_n = semicircles2rad(delta_n * std::pow(2.0, -43));
1037 ephemeris->Cuc = Cuc * std::pow(2.0, -29);
1038 ephemeris->Cus = Cus * std::pow(2.0, -29);
1039 ephemeris->Crc = Crc * std::pow(2.0, -5);
1040 ephemeris->Crs = Crs * std::pow(2.0, -5);
1041 ephemeris->signalAccuracy = galSisaIdx2Val(SISA);
1042
1043 LOG_DATA("{}: [{}] IODnav [{}], Omega_dot [{} rad/s], delta_n [{:.3e} rad/s], Cuc [{:.3e} rad], Cus [{:.3e} rad/s], Crc [{:.3e} m], Crs [{:.3e} m], SISA [{:.3e} m]", nameId(), satId,
1044 ephemeris->IODnav, ephemeris->Omega_dot, ephemeris->delta_n, ephemeris->Cuc, ephemeris->Cus, ephemeris->Crc, ephemeris->Crs, ephemeris->signalAccuracy);
1045
1046 finishWord(ephemeris, wordType, ephemerisBuilder->get().subframes);
1047 }
1048 else if (wordType == 4) // SVID, Ephemeris (4/4), and Clock correction parameters
1049 {
1050 // Galileo-OS-SIS-ICD-v2.0: ch. 4.3.5, Table 43 Bits Allocation for I/NAV Word Type 4, p. 38
1051 //
1052 // Even/Odd PageType WordType IODnav SVID Cic Cic Cis toc toc af0 af0 af1 Tail Pad Even/Odd PageType af1 af2 Spare Reserved 1
1053 // 1 1 6 10 6 8 8 16 8 6 26 5 13 6 8 1 1 8 6 2 14
1054 // └───────────────────────┬─────────────────────┘ └─────┬─────┘ └──┬───┘ └────────┬────────┘ └───────────────────────┬─────────────────────┘
1055 // Ublox 1 2 3 4 5
1056
1057 // Galileo-OS-SIS-ICD-v2.0: ch. 5.1.1, Table 60 Ephemeris Parameters, p. 43f
1058 // Galileo-OS-SIS-ICD-v2.0: ch. 5.1.3, Table 63 Galileo Clock Correction Parameters, p. 46
1059 // > Cic, Cis, af0, af1, af2 shall be two's complement, with the sign bit (+ or -) occupying the MSB
1060
1061 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 1 */, std::bitset<32>(sfrbx.dwrd.at(w)));
1062 auto IODnav = static_cast<uint16_t>((sfrbx.dwrd.at(w) >> 14) & 0b1111111111);
1063 [[maybe_unused]] auto SVID = static_cast<uint8_t>((sfrbx.dwrd.at(w) >> 8) & 0b111111);
1064 auto Cic = static_cast<int16_t>((sfrbx.dwrd.at(w) & 0b11111111) << 8);
1065 LOG_DATA("{}: [{}] IODnav {}, SVID {}, Cic {}", nameId(), satId, IODnav, SVID, std::bitset<16>(static_cast<uint16_t>(Cic)));
1066
1067 w++;
1068 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 2 */, std::bitset<32>(sfrbx.dwrd.at(w)));
1069 Cic |= static_cast<int16_t>((sfrbx.dwrd.at(w) >> 24) & 0b11111111); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
1070 auto Cis = static_cast<int16_t>((sfrbx.dwrd.at(w) >> 8) & 0b1111111111111111);
1071 auto toc = static_cast<uint16_t>((sfrbx.dwrd.at(w) & 0b11111111) << 6);
1072 LOG_DATA("{}: [{}] Cic {} ({}), Cis {}, toc {}", nameId(), satId, Cic, std::bitset<16>(static_cast<uint16_t>(Cic)), Cis, std::bitset<16>(toc));
1073
1074 w++;
1075 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 3 */, std::bitset<32>(sfrbx.dwrd.at(w)));
1076 toc |= static_cast<uint16_t>((sfrbx.dwrd.at(w) >> 26) & 0b111111);
1077 auto af0 = static_cast<int32_t>((sfrbx.dwrd.at(w) & 0b11111111111111111111111111) << 5);
1078 LOG_DATA("{}: [{}] toc {} ({}), af0 {}", nameId(), satId, toc, std::bitset<16>(toc), std::bitset<32>(static_cast<uint32_t>(af0)));
1079
1080 w++;
1081 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 4 */, std::bitset<32>(sfrbx.dwrd.at(w)));
1082 af0 |= static_cast<int32_t>((sfrbx.dwrd.at(w) >> 27) & 0b11111);
1084 auto af1 = static_cast<int32_t>(((sfrbx.dwrd.at(w) >> 14) & 0b1111111111111) << 8);
1085 LOG_DATA("{}: [{}] af0 {} ({}), af1 {}", nameId(), satId, af0, std::bitset<32>(static_cast<uint32_t>(af0)), std::bitset<32>(static_cast<uint32_t>(af1)));
1086
1087 w++;
1088 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 5 */, std::bitset<32>(sfrbx.dwrd.at(w)));
1089 af1 |= static_cast<int32_t>((sfrbx.dwrd.at(w) >> 22) & 0b11111111);
1091 auto af2 = math::interpretAs<int8_t, 6>(sfrbx.dwrd.at(w) >> 16);
1092 LOG_DATA("{}: [{}] af1 {} ({}), af1 {}", nameId(), satId, af1, std::bitset<32>(static_cast<uint32_t>(af1)), std::bitset<8>(static_cast<uint8_t>(af2)));
1093
1094 auto ephemerisBuilder = getEphemerisBuilder(satId, IODnav);
1095 if (!ephemerisBuilder.has_value())
1096 {
1097 LOG_DATA("{}: [{}] Could not find Ephemeris builder for IODnav {}", nameId(), satId, IODnav);
1098 return;
1099 }
1100 auto ephemeris = std::dynamic_pointer_cast<GalileoEphemeris>(ephemerisBuilder->get().navData);
1101
1102 InsTime insTimeToc(gpsWeekToW.gpsCycle, gpsWeekToW.gpsWeek, toc * 60.0, GST);
1103
1104 ephemeris->Cic = Cic * std::pow(2.0, -29);
1105 ephemeris->Cis = Cis * std::pow(2.0, -29);
1106 ephemeris->toc = insTimeToc;
1107 ephemeris->refTime = ephemeris->toc;
1108 ephemeris->a = {
1109 af0 * std::pow(2, -34),
1110 af1 * std::pow(2, -46),
1111 af2 * std::pow(2, -59),
1112 };
1113
1114 LOG_DATA("{}: [{}] IODnav [{}], Cic [{:.3e} rad], Cis [{:.3e} rad]", nameId(), satId, ephemeris->IODnav, ephemeris->Cic, ephemeris->Cis);
1115 LOG_DATA("{}: [{}] toc [{}], a0 [{:.3e} s], a1 [{:.3e} s/s], a2 [{:.3e} s/s^2]", nameId(), satId,
1116 ephemeris->toc.toYMDHMS(GPST), ephemeris->a[0], ephemeris->a[1], ephemeris->a[2]);
1117
1118 finishWord(ephemeris, wordType, ephemerisBuilder->get().subframes);
1119 }
1120 else if (wordType == 5) // Ionospheric correction, BGD, signal health and data validity status and GST
1121 {
1122 // Galileo-OS-SIS-ICD-v2.0: ch. 4.3.5, Table 44 Bits Allocation for I/NAV Word Type 5, p. 38
1123 //
1124 // Even/Odd PageType WordType ai0 ai1 ai2 ai2 Region 1-5 BGD(E1,E5a) BGD(E1,E5b) BGD(E1,E5b) E5b_HS E1b_HS E5b_DVS E1b_DVS WN TOW TOW Spare Tail Pad Even/Odd PageType Spare Reserved 1
1125 // 1 1 6 11 11 2 12 5x1 10 5 5 2 2 1 1 12 9 11 7 13 6 1 1 16 14
1126 // └─────────────────────┬───────────────────┘ └───────────────────┬───────────────────┘ └─────────────────────────┬──────────────────────────┘ └─────────┬─────────┘ └──────────────────┬────────────────┘
1127 // Ublox 1 2 3 4 5
1128
1129 // Galileo-OS-SIS-ICD-v2.0: ch. 5.1.6, Table 67 Ionospheric Correction Parameters, p. 48
1130 // Galileo-OS-SIS-ICD-v2.0: ch. 5.1.5, Table 65 BGD Parameters, p. 47
1131 // > ai1, ai2, BGD(E1,E5a), BGD(E1,E5b) shall be two's complement, with the sign bit (+ or -) occupying the MSB
1132
1133 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 1 */, std::bitset<32>(sfrbx.dwrd.at(w)));
1134 auto ai0 = static_cast<uint16_t>((sfrbx.dwrd.at(w) >> 13) & 0b11111111111);
1135 auto ai1 = math::interpretAs<int16_t, 11>((sfrbx.dwrd.at(w) >> 2) & 0b11111111111);
1136 auto ai2 = static_cast<int16_t>((sfrbx.dwrd.at(w) & 0b11) << 12);
1137 LOG_DATA("{}: [{}] ai0 {} ({}), ai1 {} ({}), ai2 {}", nameId(), satId,
1138 ai0, std::bitset<16>(ai0), ai1, std::bitset<16>(static_cast<uint16_t>(ai1)), std::bitset<16>(static_cast<uint16_t>(ai2)));
1139
1140 w++;
1141 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 2 */, std::bitset<32>(sfrbx.dwrd.at(w)));
1142 ai2 |= static_cast<int16_t>((sfrbx.dwrd.at(w) >> 20) & 0b111111111111); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
1144 auto BGD_E1_E5a = math::interpretAs<int16_t, 10>((sfrbx.dwrd.at(w) >> 5) & 0b1111111111);
1145 auto BGD_E1_E5b = static_cast<int16_t>((sfrbx.dwrd.at(w) & 0b11111) << 5);
1146 LOG_DATA("{}: [{}] ai2 {} ({}), BGD_E1_E5a {}, BGD_E1_E5b {}", nameId(), satId, ai2, std::bitset<16>(static_cast<uint16_t>(ai2)), BGD_E1_E5a, std::bitset<16>(static_cast<uint16_t>(BGD_E1_E5b)));
1147
1148 w++;
1149 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 3 */, std::bitset<32>(sfrbx.dwrd.at(w)));
1150 BGD_E1_E5b |= static_cast<int16_t>((sfrbx.dwrd.at(w) >> 27) & 0b11111); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
1151 BGD_E1_E5b = math::interpretAs<int16_t, 10>(BGD_E1_E5b);
1152 auto E5b_HS = static_cast<uint8_t>((sfrbx.dwrd.at(w) >> 25) & 0b11);
1153 auto E1b_HS = static_cast<uint8_t>((sfrbx.dwrd.at(w) >> 23) & 0b11);
1154 auto E5b_DVS = static_cast<uint8_t>((sfrbx.dwrd.at(w) >> 22) & 0b1);
1155 auto E1b_DVS = static_cast<uint8_t>((sfrbx.dwrd.at(w) >> 21) & 0b1);
1156 [[maybe_unused]] auto WN = static_cast<uint16_t>((sfrbx.dwrd.at(w) >> 9) & 0b111111111111);
1157 [[maybe_unused]] uint32_t TOW = (sfrbx.dwrd.at(w) & 0b111111111) << 11;
1158 LOG_DATA("{}: [{}] BGD_E1_E5b {} ({}), E5b_HS {}, E1b_HS {}, E5b_DVS {}, E1b_DVS {}, WN {}, TOW {}", nameId(), satId,
1159 BGD_E1_E5b, std::bitset<16>(static_cast<uint16_t>(BGD_E1_E5b)), E5b_HS, E1b_HS, E5b_DVS, E1b_DVS, WN, std::bitset<32>(TOW));
1160
1161 w++;
1162 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 4 */, std::bitset<32>(sfrbx.dwrd.at(w)));
1163 TOW |= (sfrbx.dwrd.at(w) >> 21) & 0b11111111111;
1164 LOG_DATA("{}: [{}] TOW {} ({})", nameId(), satId, TOW, std::bitset<32>(TOW));
1165
1166 w++;
1167 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 5 */, std::bitset<32>(sfrbx.dwrd.at(w)));
1168
1169 auto ephemerisBuilder = getLastEphemerisBuilder(satId);
1170 if (!ephemerisBuilder.has_value())
1171 {
1172 LOG_DATA("{}: [{}] Could not find any ephemeris builder", nameId(), satId);
1173 return;
1174 }
1175 auto ephemeris = std::dynamic_pointer_cast<GalileoEphemeris>(ephemerisBuilder->get().navData);
1176
1177 ephemeris->BGD_E1_E5a = BGD_E1_E5a * std::pow(2.0, -32);
1178 ephemeris->BGD_E1_E5b = BGD_E1_E5b * std::pow(2.0, -32);
1179 ephemeris->svHealth = {
1180 .E5a_DataValidityStatus = {},
1181 .E5b_DataValidityStatus = static_cast<GalileoEphemeris::SvHealth::DataValidityStatus>(E5b_DVS),
1182 .E1B_DataValidityStatus = static_cast<GalileoEphemeris::SvHealth::DataValidityStatus>(E1b_DVS),
1183 .E5a_SignalHealthStatus = {},
1184 .E5b_SignalHealthStatus = static_cast<GalileoEphemeris::SvHealth::SignalHealthStatus>(E5b_HS),
1185 .E1B_SignalHealthStatus = static_cast<GalileoEphemeris::SvHealth::SignalHealthStatus>(E1b_HS),
1186 };
1187 ephemeris->dataSource[0] = true; // I/NAV E1-B
1188 ephemeris->dataSource[9] = true; // af0-af2, Toc, SISA are for E5b,E1
1189
1190 {
1191 std::unique_lock guard(outputPins.at(OUTPUT_PORT_INDEX_GNSS_NAV_INFO).dataAccessMutex, std::defer_lock);
1192 if (!_postProcessingLock.has_value())
1193 {
1194 guard.lock();
1195 }
1196
1197 if (!_gnssNavInfo.ionosphericCorrections.contains(satId.satSys, IonosphericCorrections::Alpha))
1198 {
1199 LOG_DEBUG("{}: [{}] Received Ionospheric corrections [{}]", nameId(), satId.satSys, ephemeris->refTime.toYMDHMS(GPST));
1200 }
1201
1202 // ‘sfu’ (solar flux unit) is not a SI unit but can be converted as: 1 sfu = 10e-22 W/(m2*Hz)
1203 _gnssNavInfo.ionosphericCorrections.insert(satId.satSys, IonosphericCorrections::Alpha,
1204 {
1205 ai0 * std::pow(2.0, -2) /* [sfu] */,
1206 ai1 * std::pow(2.0, -8) /* [sfu/degree] */,
1207 ai2 * std::pow(2.0, -15) /* [sfu/degree^2] */,
1208 0.0,
1209 });
1210 }
1211
1212 LOG_DATA("{}: [{}] BGD_E1_E5a [{:.3e} s], BGD_E1_E5b [{:.3e} rad]", nameId(), satId, ephemeris->BGD_E1_E5a, ephemeris->BGD_E1_E5b);
1213
1214 finishWord(ephemeris, wordType, ephemerisBuilder->get().subframes);
1215 }
1216 else if (wordType == 6) // GST-UTC conversion parameters
1217 {
1218 // Galileo-OS-SIS-ICD-v2.0: ch. 4.3.5, Table 44 Bits Allocation for I/NAV Word Type 6, p. 38
1219 //
1220 // Even/Odd PageType WordType A0 A0 A1 dt_LS t0t WNot WN_LSF DN dt_LSF TOW Tail Pad Even/Odd PageType TOW Spare Reserved 1
1221 // 1 1 6 24 8 24 8 8 8 8 3 8 7 6 8 1 1 13 3 14
1222 // └───────────────┬──────────────┘ └──┬─┘ └──────────┬───────────┘ └────────────┬───────────┘ └────────────────────┬───────────────────┘
1223 // Ublox 1 2 3 4 5
1224
1225 // Galileo-OS-SIS-ICD-v2.0: ch. 5.1.7, Table 68 Ionospheric Correction Parameters, p. 49
1226 // > A0, A1, dt_LS, dt_LSF shall be two's complement, with the sign bit (+ or -) occupying the MSB
1227
1228 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 1 */, std::bitset<32>(sfrbx.dwrd.at(w)));
1229 auto A0 = static_cast<int32_t>((sfrbx.dwrd.at(w) & 0b111111111111111111111111) << 8);
1230 LOG_DATA("{}: [{}] A0 {}", nameId(), satId, std::bitset<32>(static_cast<uint32_t>(A0)));
1231
1232 w++;
1233 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 2 */, std::bitset<32>(sfrbx.dwrd.at(w)));
1234 A0 |= static_cast<int32_t>((sfrbx.dwrd.at(w) >> 24) & 0b11111111);
1235 auto A1 = math::interpretAs<int32_t, 24>(sfrbx.dwrd.at(w) & 0b111111111111111111111111);
1236 LOG_DATA("{}: [{}] A0 {} ({}), A1 {} ({})", nameId(), satId, A0, std::bitset<32>(static_cast<uint32_t>(A0)), A1, std::bitset<32>(static_cast<uint32_t>(A1)));
1237
1238 w++;
1239 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 3 */, std::bitset<32>(sfrbx.dwrd.at(w)));
1240
1241 w++;
1242 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 4 */, std::bitset<32>(sfrbx.dwrd.at(w)));
1243
1244 w++;
1245 LOG_DATA("{}: [{}] word {:2}: {}", nameId(), satId, w + 1 /* 5 */, std::bitset<32>(sfrbx.dwrd.at(w)));
1246
1247 std::unique_lock guard(outputPins.at(OUTPUT_PORT_INDEX_GNSS_NAV_INFO).dataAccessMutex, std::defer_lock);
1248 if (!_postProcessingLock.has_value())
1249 {
1250 guard.lock();
1251 }
1252
1253 auto key = std::make_pair(satId.satSys.getTimeSystem(), UTC);
1254 if (!_gnssNavInfo.timeSysCorr.contains(key))
1255 {
1256 if (auto ephemerisBuilder = getLastEphemerisBuilder(satId);
1257 ephemerisBuilder.has_value())
1258 {
1259 auto ephemeris = std::dynamic_pointer_cast<GalileoEphemeris>(ephemerisBuilder->get().navData);
1260 LOG_DEBUG("{}: [{}] Received Time system corrections [{}]", nameId(), satId.satSys, ephemeris->refTime.toYMDHMS(GPST));
1261 }
1262 else
1263 {
1264 LOG_DEBUG("{}: [{}] Received Time system corrections", nameId(), satId.satSys);
1265 }
1266 }
1267
1269 .a0 = A0 * std::pow(2, -30),
1270 .a1 = A1 * std::pow(2, -50),
1271 };
1272 }
1273}
1274
1275void NAV::UbloxGnssOrbitCollector::decryptGLONASS([[maybe_unused]] const SatId& satId, const ubx::UbxRxmSfrbx& /* sfrbx */, const InsTime& /* insTime */)
1276{
1277 // u-blox 8 / u-blox M8: Receiver description - Including protocol specification, ch. 10.3, p. 31
1278 // > For GLONASS L1OF and L2OF signals, each reported subframe contains a string as described in
1279 // > the GLONASS ICD. This string comprises 85 data bits which are reported over three 32 bit words
1280 // > in the UBX-RXM-SFRBX message. Data bits 1 to 8 are always a hamming code, whilst bits 81 to 84
1281 // > are a string number and bit 85 is the idle chip, which should always have a value of zero. The
1282 // > meaning of other bits vary with string and frame number.
1283 // > The fourth and final 32 bit word in the UBX-RXM-SFRBX message contains frame and superframe
1284 // > numbers (where available). These values aren't actually transmitted by the SVs, but are deduced
1285 // > by the receiver and are included to aid decoding of the transmitted data. However, the receiver
1286 // > does not always know these values, in which case a value of zero is reported.
1287 // >
1288 // > MSB LSB
1289 // > 1: Idle chip (1 bit, always 0) String # (4 bits) Data (80 - 54) (27 bits)
1290 // > 2: Data (53 - 22) (32 bits)
1291 // > 3: Data (21 - 9) (13 bits) Hammering code (8 bits) Pad (11 bits)
1292 // > 4: Superframe # (16 bits) Pad (8 bits) Frame # (8 bits)
1293 // >
1294 // > In some circumstances, (especially on startup) the receiver may be able to decode data from a
1295 // > GLONASS SV before it can identify the SV. When this occurs UBX-RXM-SFRBX messages will be
1296 // > issued with an svId of 255 to indicate "unknown".
1297
1298 if (!_warningsNotImplemented.contains(satId.satSys))
1299 {
1300 LOG_WARN("{}: ubx orbit collector is not implemented for GLONASS yet.", nameId()); // TODO: not implemented yet.
1301 _warningsNotImplemented.insert(satId.satSys);
1302 }
1303
1304 // auto ephemeris = std::dynamic_pointer_cast<GLONASSEphemeris>(ephemerisBuilder->get().navData);
1305
1306 // sfrbx.freqId;
1307 // _gnssNavInfo.addSatelliteNavData({ satSys, satNum }, std::make_shared<GLONASSEphemeris>(epoch, tau_c,
1308 // -m_tau_n, gamma_n, static_cast<bool>(health),
1309 // pos, vel, accelLuniSolar,
1310 // frequencyNumber_accuracyCode));
1311}
1312
1313void NAV::UbloxGnssOrbitCollector::decryptBeiDou([[maybe_unused]] const SatId& satId, const ubx::UbxRxmSfrbx& /* sfrbx */, const InsTime& /* insTime */)
1314{
1315 // u-blox 8 / u-blox M8: Receiver description - Including protocol specification, ch. 10.4, p. 32
1316 // > For BeiDou (B1I) signals, there is a fairly straightforward mapping between the reported subframe
1317 // > and the structure of subframe and words described in the BeiDou ICD. Each subframe comprises
1318 // > ten data words, which are reported in the same order they are received.
1319 // >
1320 // > MSB LSB
1321 // > 1 to 10: Pad (2 bits) Data (22 bits) Parity (8 bits)
1322 // >
1323 // > Note that as the BeiDou data words only comprise 30 bits, the 2 most significant bits in each word
1324 // > reported by UBX-RXM-SFRBX are padding and should be ignored.
1325
1326 if (!_warningsNotImplemented.contains(satId.satSys))
1327 {
1328 LOG_WARN("{}: ubx orbit collector is not implemented for BeiDou yet.", nameId()); // TODO: not implemented yet.
1329 _warningsNotImplemented.insert(satId.satSys);
1330 }
1331
1332 // auto ephemeris = std::dynamic_pointer_cast<BDSEphemeris>(ephemerisBuilder->get().navData);
1333
1334 // _gnssNavInfo.addSatelliteNavData({ satSys, satNum }, std::make_shared<BDSEphemeris>(epoch, toe,
1335 // IODE_IODnav_AODE_IODEC, fitInterval_AODC, a,
1336 // sqrt_A, e, i_0, Omega_0, omega, M_0,
1337 // delta_n, Omega_dot, i_dot, Cus, Cuc,
1338 // Cis, Cic, Crs, Crc,
1339 // signalAccuracy, svHealth,
1340 // tgd_bgd5a_TGD1, IODC_bgd5b_TGD2));
1341}
1342
1343void NAV::UbloxGnssOrbitCollector::decryptQZSS([[maybe_unused]] const SatId& satId, const ubx::UbxRxmSfrbx& /* sfrbx */, const InsTime& /* insTime */)
1344{
1345 // u-blox 8 / u-blox M8: Receiver description - Including protocol specification, ch. 10.7, p. 34
1346 // > The structure of the data delivered by QZSS L1C/A signals is effectively identical to that of GPS
1347 // > (L1C/A). Similarly the QZSS L2C signal is effectively identical to the GPS (L2C).
1348 // > The QZSS (L1SAIF) signal is different and uses the same data block format as used by SBAS
1349 // > (L1C/A). QZSS (SAIF) signals can be distinguished from QZSS (L1C/A and L2C) by noting that they
1350 // > have 8 words, instead of 10 for QZSS (L1C/A and L2C).
1351
1352 if (!_warningsNotImplemented.contains(satId.satSys))
1353 {
1354 LOG_WARN("{}: ubx orbit collector is not implemented for QZSS yet.", nameId()); // TODO: not implemented yet.
1355 _warningsNotImplemented.insert(satId.satSys);
1356 }
1357}
1358
1359void NAV::UbloxGnssOrbitCollector::decryptIRNSS([[maybe_unused]] const SatId& satId, const ubx::UbxRxmSfrbx& /* sfrbx */, const InsTime& /* insTime */)
1360{
1361 if (!_warningsNotImplemented.contains(satId.satSys))
1362 {
1363 LOG_WARN("{}: ubx orbit collector is not implemented for IRNSS yet.", nameId()); // TODO: not implemented yet.
1364 _warningsNotImplemented.insert(satId.satSys);
1365 }
1366}
1367
1368void NAV::UbloxGnssOrbitCollector::decryptSBAS([[maybe_unused]] const SatId& satId, const ubx::UbxRxmSfrbx& /* sfrbx */, const InsTime& /* insTime */)
1369{
1370 // u-blox 8 / u-blox M8: Receiver description - Including protocol specification, ch. 10.6, p. 33f
1371
1372 if (!_warningsNotImplemented.contains(satId.satSys))
1373 {
1374 LOG_WARN("{}: ubx orbit collector is not implemented for SBAS yet.", nameId()); // TODO: not implemented yet.
1375 _warningsNotImplemented.insert(satId.satSys);
1376 }
1377}
BDS Ephemeris information.
Save/Load the Nodes.
Galileo Ephemeris information.
GNSS helper functions.
GPS Ephemeris information.
Galileo Ephemeris information.
Utility class for logging to console and file.
#define LOG_CRITICAL(...)
Critical Event, which causes the program to work entirely and throws an exception.
Definition Logger.hpp:75
#define LOG_DEBUG
Debug information. Should not be called on functions which receive observations (spamming)
Definition Logger.hpp:67
#define LOG_DATA
All output which occurs repeatedly every time observations are received.
Definition Logger.hpp:29
#define LOG_ERROR
Error occurred, which stops part of the program to work, but not everything.
Definition Logger.hpp:73
#define LOG_WARN
Error occurred, but a fallback option exists and program continues to work normally.
Definition Logger.hpp:71
#define LOG_TRACE
Detailled info to trace the execution of the program. Should not be called on functions which receive...
Definition Logger.hpp:65
Manages all Nodes.
Algorithms concerning the STL containers.
Collects UBX-RXM-SFRBX messages and provides the Orbit information.
static std::string type()
Returns the type of the data class.
Input pins of nodes.
Definition Pin.hpp:491
TsDeque< std::shared_ptr< const NAV::NodeData > > NodeDataQueue
Node data queue type.
Definition Pin.hpp:707
The class is responsible for all time-related tasks.
Definition InsTime.hpp:710
constexpr InsTime_GPSweekTow toGPSweekTow(TimeSystem timesys=GPST) const
Converts this time object into a different format.
Definition InsTime.hpp:854
constexpr InsTime_YMDHMS toYMDHMS(TimeSystem timesys=UTC, int digits=-1) const
Converts this time object into a different format.
Definition InsTime.hpp:871
@ Beta
Coefficients of a cubic equation representing the period of the model.
@ Alpha
Coefficients of a cubic equation representing the amplitude of the vertical delay.
bool isDisabled() const
Checks if the node is disabled.
Definition Node.cpp:499
std::vector< OutputPin > outputPins
List of output pins.
Definition Node.hpp:399
Node(std::string name)
Constructor.
Definition Node.cpp:30
std::vector< InputPin > inputPins
List of input pins.
Definition Node.hpp:397
@ POST_PROCESSING
Node running in post-processing mode.
Definition Node.hpp:192
std::string nameId() const
Node name and id.
Definition Node.cpp:253
Mode getMode() const
Get the current mode of the node.
Definition Node.cpp:295
std::string name
Name of the Node.
Definition Node.hpp:395
std::scoped_lock< std::mutex > requestOutputValueLock(size_t pinIdx)
Blocks the thread till the output values was read by all connected nodes.
Definition Node.cpp:133
bool _hasConfig
Flag if the config window should be shown.
Definition Node.hpp:413
Output pins of nodes.
Definition Pin.hpp:338
@ GalileoEphemeris
Galileo Broadcast Ephemeris.
@ GPSEphemeris
GPS Broadcast Ephemeris.
auto extract_front()
Returns a copy of the first element in the container and removes it from the container.
Definition TsDeque.hpp:494
std::optional< std::reference_wrapper< EphemerisBuilder > > getLastEphemerisBuilder(const SatId &satId)
Searches the most recent ephemeris builder for the given satellite.
static std::string typeStatic()
String representation of the Class Type.
std::vector< EphemerisBuilder > _ephemerisBuilder
Map of ephemeris build helper for each satellite.
GnssNavInfo _gnssNavInfo
Data object to share over the output pin.
void receiveObs(InputPin::NodeDataQueue &queue, size_t pinIdx)
Data receive function.
static std::string category()
String representation of the Class Category.
void decryptGPS(const SatId &satId, const ubx::UbxRxmSfrbx &sfrbx, const InsTime &insTime)
Decrypt the GPS SFRBX message.
void guiConfig() override
ImGui config window which is shown on double click.
void decryptSBAS(const SatId &satId, const ubx::UbxRxmSfrbx &sfrbx, const InsTime &insTime)
Decrypt the SBAS SFRBX message.
void decryptGalileo(const SatId &satId, const ubx::UbxRxmSfrbx &sfrbx, const InsTime &insTime)
Decrypt the Galileo SFRBX message.
bool initialize() override
Initialize the node.
void decryptGLONASS(const SatId &satId, const ubx::UbxRxmSfrbx &sfrbx, const InsTime &insTime)
Decrypt the GLONASS SFRBX message.
static constexpr size_t INPUT_PORT_INDEX_UBLOX_OBS
Flow (UbloxObs)
void decryptQZSS(const SatId &satId, const ubx::UbxRxmSfrbx &sfrbx, const InsTime &insTime)
Decrypt the QZSS SFRBX message.
EphemerisBuilder & getEphemerisBuilder(const SatId &satId, const InsTime &insTime, size_t IOD=0)
Searches the ephemeris builder for the given satellite and time. If nothing found returns a new insta...
void decryptIRNSS(const SatId &satId, const ubx::UbxRxmSfrbx &sfrbx, const InsTime &insTime)
Decrypt the IRNSS SFRBX message.
std::set< SatelliteSystem > _warningsNotImplemented
List of satellite systems to emit warnings because conversion is not implemented yet.
static constexpr size_t OUTPUT_PORT_INDEX_GNSS_NAV_INFO
Object.
UbloxGnssOrbitCollector()
Default constructor.
std::unordered_map< SatId, size_t > _lastAccessedBuilder
List of IOD for each satellite.
void onDeleteLink(OutputPin &startPin, InputPin &endPin) override
Called when a link is to be deleted.
~UbloxGnssOrbitCollector() override
Destructor.
std::string type() const override
String representation of the Class Type.
void decryptBeiDou(const SatId &satId, const ubx::UbxRxmSfrbx &sfrbx, const InsTime &insTime)
Decrypt the BeiDou SFRBX message.
std::optional< std::unique_lock< std::mutex > > _postProcessingLock
Mutex to lock if the connected ublox obs provider is a file reader.
static std::string type()
Returns the type of the data class.
Definition UbloxObs.hpp:30
OutputPin * CreateOutputPin(Node *node, const char *name, Pin::Type pinType, const std::vector< std::string > &dataIdentifier, OutputPin::PinData data=static_cast< void * >(nullptr), int idx=-1)
Create an Output Pin object.
InputPin * CreateInputPin(Node *node, const char *name, Pin::Type pinType, const std::vector< std::string > &dataIdentifier={}, InputPin::Callback callback=static_cast< InputPin::FlowFirableCallbackFunc >(nullptr), InputPin::FlowFirableCheckFunc firable=nullptr, int priority=0, int idx=-1)
Create an Input Pin object.
constexpr Out interpretAs(In in)
Interprets the input integer with certain amount of Bits as Output type. Takes care of sign extension...
Definition Math.hpp:74
std::string getStringFromMsgId(UbxClass msgClass, uint8_t msgId)
Get the a string from a UBX Msg Id.
SatelliteSystem getSatSys(uint8_t gnssId)
Get the GNSS Satellite System from gnssId.
@ UBX_CLASS_RXM
Receiver Manager Messages: Satellite Status, RTC Status.
UbxRxmMessages
The available RXM Messages.
std::string getStringFromMsgClass(UbxClass msgClass)
Get the a string from a UBX Msg Class.
@ GST
Galileo System Time.
@ GPST
GPS Time.
@ UTC
Coordinated Universal Time.
double galSisaIdx2Val(uint8_t idx)
Converts a GALILEO SISA (Signal in space accuracy) index to it's value.
Definition Functions.cpp:38
double gpsUraIdx2Val(uint8_t idx)
Converts a GPS URA (user range accuracy) index to it's value.
Definition Functions.cpp:54
constexpr auto semicircles2rad(const T &semicircles)
Convert Semicircles to Radians.
Definition Units.hpp:57
SatelliteSystem_
Satellite System enumeration.
@ GPS
Global Positioning System.
@ QZSS
Quasi-Zenith Satellite System.
@ GLO
Globalnaja nawigazionnaja sputnikowaja sistema (GLONASS)
@ GAL
Galileo.
@ SBAS
Satellite Based Augmentation System.
@ BDS
Beidou.
@ SatSys_None
No Satellite system.
@ IRNSS
Indian Regional Navigation Satellite System.
DataValidityStatus
Navigation Data Validity.
SignalHealthStatus
Signal Health Status.
Time system correction parameters.
@ Flow
NodeData Trigger.
Definition Pin.hpp:52
@ Object
Generic Object.
Definition Pin.hpp:57
Identifies a satellite (satellite system and number)
SatelliteSystem satSys
Satellite system (GPS, GLONASS, GALILEO, QZSS, BDS, IRNSS, SBAS)
Satellite System type.
TimeSystem getTimeSystem() const
Get the Time System of this Satellite System.
Ephemeris builder to store unfinished ephemeris data till all subframes are collected.
Broadcast Navigation Data Subframe.
std::vector< uint32_t > dwrd
The data words.
uint8_t numWords
The number of data words contained in this message (0..16)