0.3.0
Loading...
Searching...
No Matches
GnssAnalyzer.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
9#include "GnssAnalyzer.hpp"
10
11#include <imgui_internal.h>
12
13#include "util/Logger.hpp"
14
16namespace nm = NAV::NodeManager;
18
20
24
27
28namespace NAV
29{
30
31/// @brief Write info to a json object
32/// @param[out] j Json output
33/// @param[in] data Object to read info from
35{
36 j = json{
37 { "obsType", data.obsType },
38 { "satSigId", data.satSigId },
39 { "sign", data.sign },
40 };
41}
42/// @brief Read info from a json object
43/// @param[in] j Json variable to read info from
44/// @param[out] data Output object
46{
47 if (j.contains("obsType")) { j.at("obsType").get_to(data.obsType); }
48 if (j.contains("satSigId")) { j.at("satSigId").get_to(data.satSigId); }
49 if (j.contains("sign")) { j.at("sign").get_to(data.sign); }
50}
51
52/// @brief Write info to a json object
53/// @param[out] j Json output
54/// @param[in] data Object to read info from
56{
57 j = json{
58 { "description", data.description() },
59 { "terms", data.terms },
60 { "unit", data.unit },
61 { "polynomialCycleSlipDetector", data.polynomialCycleSlipDetector },
62 { "polynomialCycleSlipDetector.thresholdPercentage", data.polynomialCycleSlipDetectorThresholdPercentage },
63 { "polynomialCycleSlipDetector.outputWhenWindowSizeNotReached", data.polynomialCycleSlipDetectorOutputWhenWindowSizeNotReached },
64 { "polynomialCycleSlipDetector.outputPolynomials", data.polynomialCycleSlipDetectorOutputPolynomials },
65 };
66}
67/// @brief Read info from a json object
68/// @param[in] j Json variable to read info from
69/// @param[out] data Output object
71{
72 if (j.contains("terms"))
73 {
74 j.at("terms").get_to(data.terms);
75 }
76 if (j.contains("unit"))
77 {
78 j.at("unit").get_to(data.unit);
79 }
80 if (j.contains("polynomialCycleSlipDetector"))
81 {
82 j.at("polynomialCycleSlipDetector").get_to(data.polynomialCycleSlipDetector);
83 }
84 if (j.contains("polynomialCycleSlipDetector.thresholdPercentage"))
85 {
86 j.at("polynomialCycleSlipDetector.thresholdPercentage").get_to(data.polynomialCycleSlipDetectorThresholdPercentage);
87 }
88 if (j.contains("polynomialCycleSlipDetector.outputWhenWindowSizeNotReached"))
89 {
90 j.at("polynomialCycleSlipDetector.outputWhenWindowSizeNotReached").get_to(data.polynomialCycleSlipDetectorOutputWhenWindowSizeNotReached);
91 }
92 if (j.contains("polynomialCycleSlipDetector.outputPolynomials"))
93 {
94 j.at("polynomialCycleSlipDetector.outputPolynomials").get_to(data.polynomialCycleSlipDetectorOutputPolynomials);
95 }
96}
97
98} // namespace NAV
99
101 : Node(typeStatic())
102{
103 LOG_TRACE("{}: called", name);
104
105 _hasConfig = true;
106 _guiConfigDefaultWindowSize = { 630, 410 };
107
109
111}
112
114{
115 LOG_TRACE("{}: called", nameId());
116}
117
119{
120 return "Gnss Analyzer";
121}
122
123std::string NAV::GnssAnalyzer::type() const
124{
125 return typeStatic();
126}
127
129{
130 return "Data Processor";
131}
132
134{
135 ImGui::TextUnformatted("Create combinations of GNSS measurements by adding or subtracting signals.");
136
137 std::vector<size_t> combToDelete;
138 for (size_t c = 0; c < _combinations.size(); c++)
139 {
140 auto& comb = _combinations.at(c);
141
142 bool keepCombination = true;
143 if (ImGui::CollapsingHeader(fmt::format("Combination {}##id{}", c, size_t(id)).c_str(),
144 _combinations.size() > 1 ? &keepCombination : nullptr, ImGuiTreeNodeFlags_DefaultOpen))
145 {
146 if (ImGui::Button(fmt::format("Add Term##id{} c{}", size_t(id), c).c_str()))
147 {
149 comb.terms.emplace_back();
150 if (comb.terms.size() != 2) { comb.polynomialCycleSlipDetector.setEnabled(false); }
151 }
152
153 ImGui::SameLine();
154 int selected = comb.unit == Combination::Unit::Meters ? 0 : 1;
155 ImGui::SetNextItemWidth(100.0F);
156 if (ImGui::Combo(fmt::format("Output unit##id{} c{}", size_t(id), c).c_str(), &selected, "Meters\0Cycles\0\0"))
157 {
159 comb.unit = selected == 0 ? Combination::Unit::Meters : Combination::Unit::Cycles;
160 }
161
162 ImGui::SameLine();
163 if (ImGui::Button(fmt::format("Cycle-slip detector##id{} c{}", size_t(id), c).c_str()))
164 {
165 ImGui::OpenPopup(fmt::format("Cycle-slip detector##Popup - id{} c{}", size_t(id), c).c_str());
166 }
167 if (ImGui::BeginPopup(fmt::format("Cycle-slip detector##Popup - id{} c{}", size_t(id), c).c_str()))
168 {
169 constexpr float WIDTH = 145.0F;
170 if (PolynomialCycleSlipDetectorGui(fmt::format("Cycle-slip detector id{} c{}", size_t(id), c).c_str(),
171 comb.polynomialCycleSlipDetector, WIDTH))
172 {
174 }
175
176 ImGui::SetNextItemWidth(WIDTH);
177 if (double val = comb.polynomialCycleSlipDetectorThresholdPercentage * 100.0;
178 ImGui::DragDouble(fmt::format("Threshold##id{} c{}", size_t(id), c).c_str(), &val, 1.0F,
179 1.0, std::numeric_limits<double>::max(), "%.2f %%"))
180 {
181 comb.polynomialCycleSlipDetectorThresholdPercentage = val / 100.0;
183 }
184 ImGui::SameLine();
185 std::string description = "As percentage of the smallest wavelength of the combination terms.";
186 if (auto maxF = std::ranges::max_element(comb.terms, [](const Combination::Term& a, const Combination::Term& b) {
187 return a.satSigId.freq().getFrequency(a.freqNum) < b.satSigId.freq().getFrequency(b.freqNum);
188 });
189 maxF != comb.terms.end())
190 {
191 double lambda = InsConst::C / maxF->satSigId.freq().getFrequency(maxF->freqNum);
192 double threshold = comb.polynomialCycleSlipDetectorThresholdPercentage * lambda;
193 description += fmt::format("\nFor [{} {}] the wavelength is λ = {:.3f} [m].\nThe threshold is then {:.3f} [m].",
194 maxF->satSigId.toSatId().satSys, maxF->satSigId.freq(), lambda, threshold);
195 }
196 gui::widgets::HelpMarker(description.c_str());
197
198 if (!comb.polynomialCycleSlipDetector.isEnabled()) { ImGui::BeginDisabled(); }
199 if (ImGui::Checkbox(fmt::format("Output when insufficient points##id{} c{}", size_t(id), c).c_str(), &comb.polynomialCycleSlipDetectorOutputWhenWindowSizeNotReached))
200 {
202 }
203 if (ImGui::Checkbox(fmt::format("Output polynomials##id{} c{}", size_t(id), c).c_str(), &comb.polynomialCycleSlipDetectorOutputPolynomials))
204 {
206 }
207 if (!comb.polynomialCycleSlipDetector.isEnabled()) { ImGui::EndDisabled(); }
208
209 ImGui::EndPopup();
210 }
211
212 std::vector<size_t> termToDelete;
213 if (ImGui::BeginTable(fmt::format("##Table id{} c{}", size_t(id), c).c_str(), 3 * static_cast<int>(comb.terms.size()) + 1,
214 ImGuiTableFlags_NoHostExtendX | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollX,
215 ImVec2(0, 70.0F)))
216 {
217 ImGui::TableNextRow();
218
219 for (size_t t = 0; t < comb.terms.size(); t++)
220 {
221 auto& term = comb.terms.at(t);
222
223 ImGui::TableSetColumnIndex(static_cast<int>(t) * 3);
224 int selected = term.sign == 1 ? 0 : 1;
225 ImGui::SetNextItemWidth(50.0F);
226 if (ImGui::Combo(fmt::format("##Sign id{} c{} t{}", size_t(id), c, t).c_str(), &selected, "+1\0-1\0\0"))
227 {
229 term.sign = selected == 0 ? +1 : -1;
230 }
231
232 ImGui::TableSetColumnIndex(static_cast<int>(t) * 3 + 1);
233 selected = term.obsType == Combination::Term::ObservationType::Pseudorange ? 0 : 1;
234 ImGui::SetNextItemWidth(62.0F);
235 if (ImGui::Combo(fmt::format("##ObsType id{} c{} t{}", size_t(id), c, t).c_str(), &selected, comb.unit == Combination::Unit::Cycles ? "P\0Φ\0\0" : "p\0φ\0\0"))
236 {
239 }
240
241 ImGui::TableSetColumnIndex(static_cast<int>(t) * 3 + 2);
242 ImGui::SetNextItemWidth(62.0F);
243 if (ShowCodeSelector(fmt::format("##Code id{} c{} t{}", size_t(id), c, t).c_str(), term.satSigId.code, Freq_All, true))
244 {
246 }
247 ImGui::SameLine();
248 ImGui::Dummy(ImVec2(10.0F, 0.0F));
249 }
250 ImGui::TableNextColumn();
251 ImGui::TextUnformatted("= Combined Frequency");
252
253 ImGui::TableNextRow();
254 for (size_t t = 0; t < comb.terms.size(); t++)
255 {
256 auto& term = comb.terms.at(t);
257
258 ImGui::TableSetColumnIndex(static_cast<int>(t) * 3);
259 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8.0F);
260 float radius = 8.0F;
261 ImGui::GetWindowDrawList()->AddCircleFilled(ImGui::GetCursorScreenPos() + ImVec2(radius, ImGui::GetTextLineHeight() / 2.0F + 2.0F), radius,
262 term.receivedDuringRun ? IM_COL32(0, 255, 0, 255) : IM_COL32(255, 0, 0, 255));
263 ImGui::Dummy(ImVec2(radius * 2.0F, ImGui::GetTextLineHeight()));
264 if (ImGui::IsItemHovered()) { ImGui::SetTooltip(term.receivedDuringRun ? "Signal was received" : "Signal was not received"); }
265
266 if (comb.terms.size() > 1)
267 {
268 ImGui::SameLine();
269 if (ImGui::Button(fmt::format("X##id{} c{} t{}", size_t(id), c, t).c_str()))
270 {
272 termToDelete.push_back(t);
273 }
274 if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Remove Term?"); }
275 }
276
277 ImGui::TableSetColumnIndex(static_cast<int>(t) * 3 + 1);
278 double f = term.satSigId.freq().getFrequency(term.freqNum);
279 ImGui::TextUnformatted(fmt::format("{:.2f}", f * 1e-6).c_str());
280 if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", fmt::format("Frequency in [Mhz]\nλ = {:.3f}m", InsConst::C / f).c_str()); }
281
282 ImGui::TableSetColumnIndex(static_cast<int>(t) * 3 + 2);
283 ImGui::SetNextItemWidth(62.0F);
284 SatId satId = SatId(term.satSigId.toSatId().satSys, term.satSigId.satNum);
285 if (ShowSatelliteSelector(fmt::format("##SatNum id{} c{} t{}", size_t(id), c, t).c_str(), satId, satId.satSys, true))
286 {
287 term.satSigId.satNum = satId.satNum;
289 }
290 }
291 ImGui::TableNextColumn();
292 double f = comb.calcCombinationFrequency();
293 ImGui::TextUnformatted(fmt::format(" {:.2f} MHz", f * 1e-6).c_str());
294 if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", fmt::format("λ = {:.3f}m", InsConst::C / f).c_str()); }
295
296 ImGui::EndTable();
297 }
298
299 for (const auto& t : termToDelete) { comb.terms.erase(std::next(comb.terms.begin(), static_cast<std::ptrdiff_t>(t))); }
300 }
301
302 if (!keepCombination) { combToDelete.push_back(c); }
303 }
304 for (const auto& c : combToDelete) { _combinations.erase(std::next(_combinations.begin(), static_cast<std::ptrdiff_t>(c))); }
305
306 ImGui::Separator();
307 if (ImGui::Button(fmt::format("Add Combination##id{}", size_t(id)).c_str()))
308 {
310 _combinations.emplace_back();
311 }
312}
313
314[[nodiscard]] json NAV::GnssAnalyzer::save() const
315{
316 LOG_TRACE("{}: called", nameId());
317
318 json j;
319
320 j["combinations"] = _combinations;
321
322 return j;
323}
324
326{
327 LOG_TRACE("{}: called", nameId());
328
329 if (j.contains("combinations"))
330 {
331 j.at("combinations").get_to(_combinations);
332 }
333}
334
336{
337 LOG_TRACE("{}: called", nameId());
338
339 for (auto& comb : _combinations)
340 {
341 comb.polynomialCycleSlipDetector.clear();
342 comb.polynomials.clear();
343 for (auto& term : comb.terms)
344 {
345 term.receivedDuringRun = false;
346 }
347 }
348
349 return true;
350}
351
353{
354 LOG_TRACE("{}: called", nameId());
355
356 for (auto& comb : _combinations)
357 {
358 for (auto& term : comb.terms)
359 {
360 term.receivedDuringRun = false;
361 }
362 }
363}
364
366{
367 auto gnssObs = std::static_pointer_cast<const GnssObs>(queue.extract_front());
368 LOG_DATA("{}: Received GnssObs for [{}]", nameId(), gnssObs->insTime);
369
370 auto gnssComb = std::make_shared<GnssCombination>();
371 gnssComb->insTime = gnssObs->insTime;
372
373 for (size_t c = 0; c < _combinations.size(); c++)
374 {
375 auto& comb = _combinations.at(c);
377 combination.description = comb.description();
378 for (size_t i = 0; i < _combinations.size(); i++)
379 {
380 if (i == c) { continue; }
381 if (combination.description == _combinations.at(i).description())
382 {
383 combination.description += fmt::format(" - {}", c);
384 break;
385 }
386 }
387
388 double result = 0.0;
389 double lambdaMin = 100.0;
390 size_t termsFound = 0;
391 for (auto& term : comb.terms)
392 {
394 oTerm.sign = term.sign;
395 oTerm.satSigId = term.satSigId;
399
400 double freq = term.satSigId.freq().getFrequency(term.freqNum);
401 double lambda = InsConst::C / freq;
402 lambdaMin = std::min(lambdaMin, lambda);
403
404 if (auto obs = (*gnssObs)(term.satSigId))
405 {
407 {
408 if (auto psr = obs->get().pseudorange)
409 {
410 term.receivedDuringRun = true;
411
412 double value = psr->value;
413 result += static_cast<double>(term.sign) * value;
414
415 if (comb.unit == Combination::Unit::Cycles)
416 {
417 value /= lambda;
418 }
419 oTerm.value = value;
420
421 termsFound++;
422 }
423 else
424 {
425 comb.polynomialCycleSlipDetector.reset(comb.description());
426 }
427 }
428 else // if (term.obsType == Combination::Term::ObservationType::Carrier)
429 {
430 if (auto carrier = obs->get().carrierPhase)
431 {
432 term.receivedDuringRun = true;
433
434 double value = carrier->value * lambda;
435 result += static_cast<double>(term.sign) * value;
436
437 if (comb.unit == Combination::Unit::Cycles)
438 {
439 value /= lambda;
440 }
441 oTerm.value = value;
442
443 termsFound++;
444 }
445 else
446 {
447 comb.polynomialCycleSlipDetector.reset(comb.description());
448 }
449 }
450 }
451
452 combination.terms.push_back(oTerm);
453 }
454 if (termsFound == comb.terms.size())
455 {
456 auto lambda = InsConst::C / comb.calcCombinationFrequency();
457 double resultCycles = result / lambda;
458 combination.result = comb.unit == Combination::Unit::Cycles ? resultCycles : result;
459
460 if (comb.polynomialCycleSlipDetector.isEnabled())
461 {
462 auto key = comb.description();
463 combination.cycleSlipPrediction = comb.polynomialCycleSlipDetector.predictValue(key, gnssComb->insTime);
464 if (combination.cycleSlipPrediction.has_value())
465 {
466 combination.cycleSlipMeasMinPred = *combination.result - *combination.cycleSlipPrediction;
467 }
468
469 if (comb.polynomialCycleSlipDetectorOutputPolynomials)
470 {
471 if (auto polynomial = comb.polynomialCycleSlipDetector.calcPolynomial(key))
472 {
473 comb.polynomials.emplace_back(gnssComb->insTime, *polynomial);
474 }
475 if (auto relTime = comb.polynomialCycleSlipDetector.calcRelativeTime(key, gnssComb->insTime))
476 {
477 for (const auto& poly : comb.polynomials)
478 {
479 double value = poly.second.f(*relTime);
480 LOG_DATA("f({:.2f}) = {:.2f} ({})", *relTime, value, poly.second.toString());
481 combination.cycleSlipPolynomials.emplace_back(poly.first, poly.second, value);
482 }
483 }
484 }
485 double threshold = comb.polynomialCycleSlipDetectorThresholdPercentage * lambdaMin;
486 if (comb.unit == Combination::Unit::Cycles) { threshold /= lambda; }
487
488 combination.cycleSlipResult = comb.polynomialCycleSlipDetector.checkForCycleSlip(key, gnssComb->insTime, *combination.result, threshold);
489 if (!comb.polynomialCycleSlipDetectorOutputWhenWindowSizeNotReached
491 {
492 combination.cycleSlipPrediction.reset();
493 combination.cycleSlipMeasMinPred.reset();
494 }
495 }
496 }
497
498 gnssComb->combinations.push_back(combination);
499 }
500
502}
Code definitions.
Holds all Constants.
Save/Load the Nodes.
nlohmann::json json
json namespace
Allows creation of GNSS measurement combinations.
GNSS measurement combinations.
GNSS Observation messages.
Text Help Marker (?) with Tooltip.
Utility class for logging to console and file.
#define LOG_DATA
All output which occurs repeatedly every time observations are received.
Definition Logger.hpp:29
#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.
Structs identifying a unique satellite.
static std::string typeStatic()
String representation of the Class Type.
static std::string category()
String representation of the Class Category.
~GnssAnalyzer() override
Destructor.
std::vector< Combination > _combinations
Combinations to calculate.
void receiveGnssObs(InputPin::NodeDataQueue &queue, size_t pinIdx)
Receive Gnss observation.
void deinitialize() override
Deinitialize the node.
void guiConfig() override
ImGui config window which is shown on double click.
void restore(const json &j) override
Restores the node from a json object.
static constexpr size_t OUTPUT_PORT_INDEX_GNSS_COMBINATION
Flow (GnssCombination)
std::string type() const override
String representation of the Class Type.
GnssAnalyzer()
Default constructor.
bool initialize() override
Initialize the node.
json save() const override
Saves the node into a json object.
static std::string type()
Returns the type of the data class.
@ Carrier
Carrier-Phase.
Definition GnssObs.hpp:39
@ Pseudorange
Pseudorange.
Definition GnssObs.hpp:38
static std::string type()
Returns the type of the data class.
Definition GnssObs.hpp:150
TsDeque< std::shared_ptr< const NAV::NodeData > > NodeDataQueue
Node data queue type.
Definition Pin.hpp:707
static constexpr double C
Speed of light [m/s].
Definition Constants.hpp:34
ImVec2 _guiConfigDefaultWindowSize
Definition Node.hpp:410
Node(std::string name)
Constructor.
Definition Node.cpp:30
std::string nameId() const
Node name and id.
Definition Node.cpp:253
std::string name
Name of the Node.
Definition Node.hpp:395
void invokeCallbacks(size_t portIndex, const std::shared_ptr< const NodeData > &data)
Calls all registered callbacks on the specified output port.
Definition Node.cpp:180
bool _hasConfig
Flag if the config window should be shown.
Definition Node.hpp:413
auto extract_front()
Returns a copy of the first element in the container and removes it from the container.
Definition TsDeque.hpp:494
bool DragDouble(const char *label, double *v, float v_speed, double v_min, double v_max, const char *format, ImGuiSliderFlags flags)
Shows a Drag GUI element for 'double'.
Definition imgui_ex.cpp:19
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.
void ApplyChanges()
Signals that there have been changes to the flow.
void HelpMarker(const char *desc, const char *symbol="(?)")
Text Help Marker, e.g. '(?)', with Tooltip.
void to_json(json &j, const Node &node)
Converts the provided node into a json object.
Definition Node.cpp:990
@ LessDataThanWindowSize
Less data than the specified window size (cannot predict cycle-slip yet)
constexpr Frequency_ Freq_All
All Frequencies.
bool ShowCodeSelector(const char *label, Code &code, const Frequency &filterFreq, bool singleSelect)
Shows a ComboBox to select signal codes.
Definition Code.cpp:1199
void from_json(const json &j, Node &node)
Converts the provided json object into a node object.
Definition Node.cpp:1007
bool PolynomialCycleSlipDetectorGui(const char *label, PolynomialCycleSlipDetector< Key > &polynomialCycleSlipDetector, float width=0.0F)
Shows a GUI for advanced configuration of the PolynomialCycleSlipDetector.
bool ShowSatelliteSelector(const char *label, std::vector< SatId > &satellites, SatelliteSystem filterSys, bool displayOnlyNumber)
Shows a ComboBox to select satellites.
Term of a combination equation.
SatSigId satSigId
SignalId and satellite number.
ObservationType obsType
Observation Type.
Combination of GNSS measurements.
PolynomialCycleSlipDetector< std::string > polynomialCycleSlipDetector
Cycle-slip detector.
std::string description() const
Get a string description of the combination.
Unit unit
Unit to calculate the combination in.
std::vector< Term > terms
List of terms making up the combination.
bool polynomialCycleSlipDetectorOutputPolynomials
Whether the polynomials should be outputted.
bool polynomialCycleSlipDetectorOutputWhenWindowSizeNotReached
Whether to output the prediction even when the window size is not reached.
double polynomialCycleSlipDetectorThresholdPercentage
Threshold to categorize a measurement as cycle slip [% of smallest wavelength].
Term of a combination equation.
std::optional< double > value
Measurement (if present)
SatSigId satSigId
SignalId and satellite number.
GnssObs::ObservationType obsType
Observation Type.
Combination of GNSS measurements.
std::optional< double > cycleSlipMeasMinPred
Measurement minus predicted value from the cycle-slip detector.
std::optional< double > cycleSlipPrediction
Predicted value from the cycle-slip detector (polynomial fit)
std::optional< PolynomialCycleSlipDetectorResult > cycleSlipResult
Cycle-slip result.
std::vector< std::tuple< InsTime, Polynomial< double >, double > > cycleSlipPolynomials
Polynomial fits.
std::string description
String describing the combination.
std::optional< double > result
Calculated combination (only set if all terms where present)
std::vector< Term > terms
List of terms making up the combination.
@ Flow
NodeData Trigger.
Definition Pin.hpp:52
Identifies a satellite (satellite system and number)
SatelliteSystem satSys
Satellite system (GPS, GLONASS, GALILEO, QZSS, BDS, IRNSS, SBAS)
uint16_t satNum
Number of the satellite.