source: ntrip/trunk/BNC/src/RTCM3/RTCM3coDecoder.cpp@ 11058

Last change on this file since 11058 was 11058, checked in by stuerze, 29 hours ago

some more fixes

File size: 50.6 KB
Line 
1// Part of BNC, a utility for retrieving decoding and
2// converting GNSS data streams from NTRIP broadcasters.
3//
4// Copyright (C) 2007
5// German Federal Agency for Cartography and Geodesy (BKG)
6// http://bkg.bund.de
7// Czech Technical University Prague, Department of Geodesy
8// http://www.fsv.cvut.cz
9//
10// Email: euref-ip@bkg.bund.de
11//
12// This program is free software; you can redistribute it and/or
13// modify it under the terms of the GNU General Public License
14// as published by the Free Software Foundation, version 2.
15//
16// This program is distributed in the hope that it will be useful,
17// but WITHOUT ANY WARRANTY; without even the implied warranty of
18// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19// GNU General Public License for more details.
20//
21// You should have received a copy of the GNU General Public License
22// along with this program; if not, write to the Free Software
23// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24
25/* -------------------------------------------------------------------------
26 * BKG NTRIP Client
27 * -------------------------------------------------------------------------
28 *
29 * Class: RTCM3coDecoder
30 *
31 * Purpose: RTCM3 Clock Orbit Decoder
32 *
33 * Author: L. Mervart
34 *
35 * Created: 05-May-2008
36 *
37 * Changes:
38 *
39 * -----------------------------------------------------------------------*/
40
41#include <stdio.h>
42#include <math.h>
43#include <climits>
44
45#include "RTCM3coDecoder.h"
46#include "bncutils.h"
47#include "bncrinex.h"
48#include "bnccore.h"
49#include "bncsettings.h"
50#include "bnctime.h"
51
52using namespace std;
53
54// Constructor
55////////////////////////////////////////////////////////////////////////////
56RTCM3coDecoder::RTCM3coDecoder(const QString& staID) {
57
58 _staID = staID;
59
60 // File Output
61 // -----------
62 bncSettings settings;
63 QString path = settings.value("corrPath").toString();
64 if (!path.isEmpty()) {
65 expandEnvVar(path);
66 if ( path.length() > 0 && path[path.length()-1] != QDir::separator() ) {
67 path += QDir::separator();
68 }
69 _fileNameSkl = path + staID;
70 }
71 _out = 0;
72
73 Qt::ConnectionType conType = BNC_CORE->decoderConnectionType();
74 connect(this, SIGNAL(newOrbCorrections(QList<t_orbCorr>)),
75 BNC_CORE, SLOT(slotNewOrbCorrections(QList<t_orbCorr>)), conType);
76
77 connect(this, SIGNAL(newClkCorrections(QList<t_clkCorr>)),
78 BNC_CORE, SLOT(slotNewClkCorrections(QList<t_clkCorr>)), conType);
79
80 connect(this, SIGNAL(newCodeBiases(QList<t_satCodeBias>)),
81 BNC_CORE, SLOT(slotNewCodeBiases(QList<t_satCodeBias>)), conType);
82
83 connect(this, SIGNAL(newPhaseBiases(QList<t_satPhaseBias>)),
84 BNC_CORE, SLOT(slotNewPhaseBiases(QList<t_satPhaseBias>)), conType);
85
86 connect(this, SIGNAL(newTec(t_vTec)),
87 BNC_CORE, SLOT(slotNewTec(t_vTec)), conType);
88
89 connect(this, SIGNAL(newMetaData(t_metaData)),
90 BNC_CORE, SLOT(slotNewMetaData(t_metaData)), conType);
91
92 connect(this, SIGNAL(newSatAntennas(QList<t_satAntenna>)),
93 BNC_CORE, SLOT(slotNewSatAntennas(QList<t_satAntenna>)), conType);
94
95 connect(this, SIGNAL(providerIDChanged(QString)),
96 BNC_CORE, SIGNAL(providerIDChanged(QString)), conType);
97
98 connect(this, SIGNAL(newMessage(QByteArray,bool)),
99 BNC_CORE, SLOT(slotMessage(const QByteArray,bool)));
100
101 reset();
102
103 _providerID[0] = -1;
104 _providerID[1] = -1;
105 _providerID[2] = -1;
106
107 for (unsigned s = 0; s < CLOCKORBIT_SATNUM; s++) {
108 _antennaReceived[s] = false;
109 _antennaIODKnown[s] = false;
110 _antennaIODProvider[s] = 0;
111 _antennaIODCur[s] = 0;
112 _antennaIODPrev[s] = 0;
113 }
114 for (unsigned i = 0; i < CLOCKORBIT_COUNTSAT; i++) {
115 _antennaSentProviderID[i] = 0;
116 _antennaSentIOD[i] = 0; // meaningless while _antennaSentTime[i] is invalid ("never sent")
117 }
118
119 _stateSnapshot = new t_stateSnapshot;
120 _ssrCorr = 0;
121
122}
123
124// Destructor
125////////////////////////////////////////////////////////////////////////////
126RTCM3coDecoder::~RTCM3coDecoder() {
127 delete _out;
128 delete _ssrCorr;
129 delete _stateSnapshot;
130 _IODs.clear();
131 _orbCorrections.clear();
132 _clkCorrections.clear();
133 _lastClkCorrections.clear();
134 _codeBiases.clear();
135 _phaseBiases.clear();
136 _vTecMap.clear();
137 _metaDataMap.clear();
138 _satAntennas.clear();
139}
140
141//
142////////////////////////////////////////////////////////////////////////////
143void RTCM3coDecoder::reset(bool resetPhaseBias) {
144 memset(&_clkOrb, 0, sizeof(_clkOrb));
145 memset(&_codeBias, 0, sizeof(_codeBias));
146 if (resetPhaseBias) {
147 memset(&_phaseBias, 0, sizeof(_phaseBias));
148 for (unsigned s = 0; s < CLOCKORBIT_SATNUM; s++) {
149 _phaseBiasSentEpoch[s] = UINT_MAX; // "never sent"
150 }
151 }
152 memset(&_vTEC, 0, sizeof(_vTEC));
153}
154
155// Reopen Output File
156////////////////////////////////////////////////////////////////////////
157void RTCM3coDecoder::reopen() {
158
159 if (!_fileNameSkl.isEmpty()) {
160
161 bncSettings settings;
162
163 QDateTime datTim = currentDateAndTimeGPS();
164
165 QString hlpStr = bncRinex::nextEpochStr(datTim,
166 settings.value("corrIntr").toString(), 3);
167
168 QString cntStr = (_fileNameSkl.contains("ION")) ? "_ION.ssr" : "_MC.ssr";
169
170 QString fileNameHlp = _fileNameSkl +
171 "_S_" + // stream
172 QString("%1").arg(datTim.date().year()) +
173 QString("%1").arg(datTim.date().dayOfYear(), 3, 10, QChar('0')) +
174 hlpStr + // HM_period
175 cntStr; // mixed ION or CLK
176
177 if (_fileName == fileNameHlp) {
178 return;
179 }
180 else {
181 _fileName = fileNameHlp;
182 }
183
184 delete _out;
185 if ( Qt::CheckState(settings.value("rnxAppend").toInt()) == Qt::Checked) {
186 _out = new ofstream( _fileName.toLatin1().data(), ios_base::out | ios_base::app );
187 }
188 else {
189 _out = new ofstream( _fileName.toLatin1().data() );
190 }
191 }
192}
193
194//
195////////////////////////////////////////////////////////////////////////////
196t_irc RTCM3coDecoder::Decode(char* buffer, int bufLen, vector<string>& errmsg) {
197
198 errmsg.clear();
199
200 _buffer.append(QByteArray(buffer,bufLen));
201
202 t_irc retCode = failure;
203
204 while(_buffer.size()) {
205
206 // save state
207 memcpy(&_stateSnapshot->clkOrb, &_clkOrb, sizeof(_clkOrb));
208 memcpy(&_stateSnapshot->codeBias, &_codeBias, sizeof(_codeBias));
209 memcpy(&_stateSnapshot->phaseBias, &_phaseBias, sizeof(_phaseBias));
210 memcpy(&_stateSnapshot->vTEC, &_vTEC, sizeof(_vTEC));
211 memcpy(&_stateSnapshot->metaData, &_metaData, sizeof(_metaData));
212 memcpy(&_stateSnapshot->antenna, &_antenna, sizeof(_antenna));
213
214 // _antenna persists across messages, so its content alone can't tell
215 // whether a Satellite Antenna message arrived in this frame (an unchanged
216 // retransmission looks identical). Clear messageType[] as a marker: the
217 // COBOFS_SATANT decoder sets it (always non-zero) for each system it
218 // decodes. Restored with the snapshot if the frame turns out incomplete.
219 for (unsigned s = 0; s < CLOCKORBIT_SATNUM; s++) {
220 _antenna.messageType[s] = 0;
221 }
222
223 int bytesused = 0;
224
225 GCOB_RETURN irc = _ssrCorr->GetSSR(&_clkOrb, &_codeBias, &_vTEC, &_phaseBias, &_metaData, &_antenna,
226 _buffer.data(), _buffer.size(), &bytesused);
227
228 if (irc <= -30) { // not enough data - restore state and exit loop
229 memcpy(&_clkOrb, &_stateSnapshot->clkOrb, sizeof(_clkOrb));
230 memcpy(&_codeBias, &_stateSnapshot->codeBias, sizeof(_codeBias));
231 memcpy(&_phaseBias, &_stateSnapshot->phaseBias, sizeof(_phaseBias));
232 memcpy(&_vTEC, &_stateSnapshot->vTEC, sizeof(_vTEC));
233 memcpy(&_metaData, &_stateSnapshot->metaData, sizeof(_metaData));
234 memcpy(&_antenna, &_stateSnapshot->antenna, sizeof(_antenna));
235 break;
236 }
237
238 else if (irc < 0) { // error - skip this message (or 1 byte if the
239 // frame itself couldn't be recognized) and retry
240 if (irc != GCOBR_UNKNOWNTYPE) {
241 // A recognized-but-unimplemented message (e.g. Tropospheric or
242 // Regional Ionospheric Correction) leaves _clkOrb/_codeBias/
243 // _phaseBias/_vTEC untouched, and bytesused is already reliably set
244 // from its CRC-validated frame length, so there is nothing to
245 // discard. Every other error code may have left inconsistent
246 // partial state (e.g. mid-satellite-loop) or an unreliable frame
247 // boundary, so reset defensively there. Without this, streams with
248 // frequent unimplemented messages (e.g. dense grid-based Regional
249 // Ionospheric traffic) would wipe unrelated, still-valid orbit/
250 // clock/bias/phase-bias state every time one is skipped.
251 reset();
252 }
253 _buffer = _buffer.mid(bytesused ? bytesused : 1);
254 }
255
256 else { // OK or MESSAGEFOLLOWS
257 _buffer = _buffer.mid(bytesused);
258
259 if (irc == GCOBR_OK || irc == GCOBR_MESSAGEFOLLOWS ) {
260 // Latch the reception until sendResults() consumes it - it may not
261 // run for this frame if _lastTime is still invalid.
262 for (unsigned s = 0; s < CLOCKORBIT_SATNUM; s++) {
263 if (_antenna.messageType[s] != 0) {
264 _antennaReceived[s] = true;
265 checkAntennaIOD(s);
266 }
267 }
268 setEpochTime(); // sets _lastTime
269
270 if (_lastTime.valid()) {
271 reopen();
272 checkProviderID();
273 sendResults();
274 retCode = success;
275 }
276 else {
277 emit newMessage("RTCM3coDecoder: _lastTime invalid: " + _staID.toLatin1(), true);
278 retCode = failure;
279 }
280 // Keep _phaseBias intact: an Extended Phase Bias message (COBOFS_PBEXT)
281 // arriving later needs to correlate against the satellite/signal list
282 // just published by this Phase Bias message. clock_orbit_rtcm_new.cpp's
283 // COBOFS_PBIAS handling resets pb->NumberOfSat[s] itself before decoding
284 // a fresh message, so this doesn't cause stale data to accumulate.
285 reset(false);
286 }
287 }
288 }
289
290 return retCode;
291}
292
293//
294////////////////////////////////////////////////////////////////////////////
295void RTCM3coDecoder::sendResults() {
296
297 // Orbit and clock corrections of all satellites
298 // ---------------------------------------------
299 for (unsigned ii = 0; ii < CLOCKORBIT_NUMGPS
300 + CLOCKORBIT_NUMGLONASS
301 + CLOCKORBIT_NUMGALILEO
302 + CLOCKORBIT_NUMQZSS
303 + CLOCKORBIT_NUMSBAS
304 + _clkOrb.NumberOfSat[CLOCKORBIT_SATBDS];
305 ii++) {
306 if (corrIsOutOfRange(_clkOrb.Sat[ii])) {
307 continue;
308 }
309 char sys = ' ';
310 int num = _clkOrb.Sat[ii].ID;
311 int flag = 0; // to force NAV type usage according SSR standard
312 if (ii < _clkOrb.NumberOfSat[CLOCKORBIT_SATGPS]) {
313 sys = 'G';
314 flag = t_eph::LNAV;
315 }
316 else if (ii >= CLOCKORBIT_OFFSETGLONASS &&
317 ii < CLOCKORBIT_OFFSETGLONASS + _clkOrb.NumberOfSat[CLOCKORBIT_SATGLONASS]) {
318 sys = 'R';
319 flag = t_eph::FDMA_M;
320 }
321 else if (ii >= CLOCKORBIT_OFFSETGALILEO &&
322 ii < CLOCKORBIT_OFFSETGALILEO + _clkOrb.NumberOfSat[CLOCKORBIT_SATGALILEO]) {
323 sys = 'E';
324 flag = t_eph::INAV;
325 }
326 else if (ii >= CLOCKORBIT_OFFSETQZSS &&
327 ii < CLOCKORBIT_OFFSETQZSS + _clkOrb.NumberOfSat[CLOCKORBIT_SATQZSS]) {
328 sys = 'J';
329 flag = t_eph::LNAV;
330 }
331 else if (ii >= CLOCKORBIT_OFFSETSBAS &&
332 ii < CLOCKORBIT_OFFSETSBAS + _clkOrb.NumberOfSat[CLOCKORBIT_SATSBAS]) {
333 sys = 'S';
334 flag = t_eph::SBASL1;
335 }
336 else if (ii >= CLOCKORBIT_OFFSETBDS &&
337 ii < CLOCKORBIT_OFFSETBDS + _clkOrb.NumberOfSat[CLOCKORBIT_SATBDS]) {
338 sys = 'C';
339 if (num < 6) {// GEO
340 flag = t_eph::D2;
341 }
342 else if (num > 58 && num < 63) { // GEO
343 flag = t_eph::D2;
344 }
345 else {
346 flag = t_eph::D1;
347 }
348 }
349 else {
350 continue;
351 }
352
353 // Orbit correction
354 // ----------------
355 if ( _clkOrb.messageType == _ssrCorr->COTYPE_GPSCOMBINED ||
356 _clkOrb.messageType == _ssrCorr->COTYPE_GLONASSCOMBINED ||
357 _clkOrb.messageType == _ssrCorr->COTYPE_GALILEOCOMBINED ||
358 _clkOrb.messageType == _ssrCorr->COTYPE_QZSSCOMBINED ||
359 _clkOrb.messageType == _ssrCorr->COTYPE_SBASCOMBINED ||
360 _clkOrb.messageType == _ssrCorr->COTYPE_BDSCOMBINED ||
361 _clkOrb.messageType == _ssrCorr->COTYPE_GPSORBIT ||
362 _clkOrb.messageType == _ssrCorr->COTYPE_GLONASSORBIT ||
363 _clkOrb.messageType == _ssrCorr->COTYPE_GALILEOORBIT ||
364 _clkOrb.messageType == _ssrCorr->COTYPE_QZSSORBIT ||
365 _clkOrb.messageType == _ssrCorr->COTYPE_SBASORBIT ||
366 _clkOrb.messageType == _ssrCorr->COTYPE_BDSORBIT ) {
367
368 t_orbCorr orbCorr;
369 orbCorr._prn.set(sys, num, flag);
370 checkPrnRange(orbCorr._prn);
371 orbCorr._staID = _staID.toStdString();
372 orbCorr._iod = _clkOrb.Sat[ii].IOD;
373 orbCorr._time = _lastTime;
374 orbCorr._updateInt = _clkOrb.UpdateInterval;
375 orbCorr._system = sys;
376 orbCorr._xr[0] = _clkOrb.Sat[ii].Orbit.DeltaRadial;
377 orbCorr._xr[1] = _clkOrb.Sat[ii].Orbit.DeltaAlongTrack;
378 orbCorr._xr[2] = _clkOrb.Sat[ii].Orbit.DeltaCrossTrack;
379 orbCorr._dotXr[0] = _clkOrb.Sat[ii].Orbit.DotDeltaRadial;
380 orbCorr._dotXr[1] = _clkOrb.Sat[ii].Orbit.DotDeltaAlongTrack;
381 orbCorr._dotXr[2] = _clkOrb.Sat[ii].Orbit.DotDeltaCrossTrack;
382 orbCorr._ssrFormat = (_type == RTCMssr) ? ssrRtcmOld :
383 (_type == RTCMnewssr) ? ssrRtcmNew : ssrUnknown;
384
385 _orbCorrections[_lastTime].append(orbCorr);
386
387 _IODs[orbCorr._prn] = _clkOrb.Sat[ii].IOD;
388 }
389
390 // Clock Corrections
391 // -----------------
392 if ( _clkOrb.messageType == _ssrCorr->COTYPE_GPSCOMBINED ||
393 _clkOrb.messageType == _ssrCorr->COTYPE_GLONASSCOMBINED ||
394 _clkOrb.messageType == _ssrCorr->COTYPE_GALILEOCOMBINED ||
395 _clkOrb.messageType == _ssrCorr->COTYPE_QZSSCOMBINED ||
396 _clkOrb.messageType == _ssrCorr->COTYPE_SBASCOMBINED ||
397 _clkOrb.messageType == _ssrCorr->COTYPE_BDSCOMBINED ||
398 _clkOrb.messageType == _ssrCorr->COTYPE_GPSCLOCK ||
399 _clkOrb.messageType == _ssrCorr->COTYPE_GLONASSCLOCK ||
400 _clkOrb.messageType == _ssrCorr->COTYPE_GALILEOCLOCK ||
401 _clkOrb.messageType == _ssrCorr->COTYPE_QZSSCLOCK ||
402 _clkOrb.messageType == _ssrCorr->COTYPE_SBASCLOCK ||
403 _clkOrb.messageType == _ssrCorr->COTYPE_BDSCLOCK) {
404
405 t_clkCorr clkCorr;
406 clkCorr._prn.set(sys, _clkOrb.Sat[ii].ID, flag);
407 checkPrnRange(clkCorr._prn);
408 clkCorr._staID = _staID.toStdString();
409 clkCorr._time = _lastTime;
410 clkCorr._updateInt = _clkOrb.UpdateInterval;
411 clkCorr._dClk = _clkOrb.Sat[ii].Clock.DeltaA0 / t_CST::c;
412 clkCorr._dotDClk = _clkOrb.Sat[ii].Clock.DeltaA1 / t_CST::c;
413 clkCorr._dotDotDClk = _clkOrb.Sat[ii].Clock.DeltaA2 / t_CST::c;
414
415 _lastClkCorrections[clkCorr._prn] = clkCorr;
416
417 if (_IODs.contains(clkCorr._prn)) {
418 clkCorr._iod = _IODs[clkCorr._prn];
419 _clkCorrections[_lastTime].append(clkCorr);
420 }
421 }
422
423 // High-Resolution Clocks
424 // ----------------------
425 if ( _clkOrb.messageType == _ssrCorr->COTYPE_GPSHR ||
426 _clkOrb.messageType == _ssrCorr->COTYPE_GLONASSHR ||
427 _clkOrb.messageType == _ssrCorr->COTYPE_GALILEOHR ||
428 _clkOrb.messageType == _ssrCorr->COTYPE_QZSSHR ||
429 _clkOrb.messageType == _ssrCorr->COTYPE_SBASHR ||
430 _clkOrb.messageType == _ssrCorr->COTYPE_BDSHR) {
431 t_prn prn(sys, _clkOrb.Sat[ii].ID, flag);
432 if (_lastClkCorrections.contains(prn)) {
433 t_clkCorr clkCorr;
434 clkCorr = _lastClkCorrections[prn];
435 clkCorr._time = _lastTime;
436 clkCorr._updateInt = _clkOrb.UpdateInterval;
437 clkCorr._dClk += _clkOrb.Sat[ii].hrclock / t_CST::c;
438 if (_IODs.contains(clkCorr._prn)) {
439 clkCorr._iod = _IODs[clkCorr._prn];
440 _clkCorrections[_lastTime].append(clkCorr);
441 }
442 }
443 }
444 }
445
446 // Code Biases
447 // -----------
448 for (unsigned ii = 0; ii < CLOCKORBIT_NUMGPS
449 + CLOCKORBIT_NUMGLONASS
450 + CLOCKORBIT_NUMGALILEO
451 + CLOCKORBIT_NUMQZSS
452 + CLOCKORBIT_NUMSBAS
453 + _codeBias.NumberOfSat[CLOCKORBIT_SATBDS];
454 ii++) {
455 char sys = ' ';
456 int num = _codeBias.Sat[ii].ID;
457 int flag = 0;
458 if (ii < _codeBias.NumberOfSat[CLOCKORBIT_SATGPS]) {
459 sys = 'G';
460 flag = t_eph::LNAV;
461 }
462 else if (ii >= CLOCKORBIT_OFFSETGLONASS &&
463 ii < CLOCKORBIT_OFFSETGLONASS + _codeBias.NumberOfSat[CLOCKORBIT_SATGLONASS]) {
464 sys = 'R';
465 flag = t_eph::FDMA_M;
466 }
467 else if (ii >= CLOCKORBIT_OFFSETGALILEO &&
468 ii < CLOCKORBIT_OFFSETGALILEO + _codeBias.NumberOfSat[CLOCKORBIT_SATGALILEO]) {
469 sys = 'E';
470 flag = t_eph::INAV;
471 }
472 else if (ii >= CLOCKORBIT_OFFSETQZSS &&
473 ii < CLOCKORBIT_OFFSETQZSS + _codeBias.NumberOfSat[CLOCKORBIT_SATQZSS]) {
474 sys = 'J';
475 flag = t_eph::LNAV;
476 }
477 else if (ii >= CLOCKORBIT_OFFSETSBAS &&
478 ii < CLOCKORBIT_OFFSETSBAS + _codeBias.NumberOfSat[CLOCKORBIT_SATSBAS]) {
479 sys = 'S';
480 flag = t_eph::SBASL1;
481 }
482 else if (ii >= CLOCKORBIT_OFFSETBDS &&
483 ii < CLOCKORBIT_OFFSETBDS + _codeBias.NumberOfSat[CLOCKORBIT_SATBDS]) {
484 sys = 'C';
485 if (num < 6) {// GEO
486 flag = t_eph::D2;
487 }
488 else if (num > 58 && num < 63) { // GEO
489 flag = t_eph::D2;
490 }
491 else {
492 flag = t_eph::D1;
493 }
494 }
495 else {
496 continue;
497 }
498 t_satCodeBias satCodeBias;
499 satCodeBias._prn.set(sys, num, flag);
500 checkPrnRange(satCodeBias._prn);
501 satCodeBias._staID = _staID.toStdString();
502 satCodeBias._time = _lastTime;
503 satCodeBias._updateInt = _codeBias.UpdateInterval;
504 satCodeBias._ssrIOD = _codeBias.SSRIOD;
505 satCodeBias._ssrProviderID = _codeBias.SSRProviderID;
506 for (unsigned jj = 0; jj < _codeBias.Sat[ii].NumberOfCodeBiases; jj++) {
507 const SsrCorr::CodeBias::BiasSat::CodeBiasEntry& biasEntry = _codeBias.Sat[ii].Biases[jj];
508 t_frqCodeBias frqCodeBias;
509 frqCodeBias._rnxType2ch.assign(_ssrCorr->codeTypeToRnxType(sys, biasEntry.Type));
510 frqCodeBias._value = biasEntry.Bias;
511 if (!frqCodeBias._rnxType2ch.empty()) {
512 satCodeBias._bias.push_back(frqCodeBias);
513 }
514 }
515 _codeBiases[_lastTime].append(satCodeBias);
516 }
517
518 // Phase Biases
519 // -----------
520 // _phaseBias is no longer wiped between messages (kept alive so a later
521 // Extended Phase Bias message can correlate against it - see
522 // COBOFS_PBEXT), so without this guard the block below would re-append
523 // the same system's satellites into the output on every other message
524 // decoded in between two actual phase-bias messages. Only emit a
525 // system's satellites once per epoch, tracked per system by comparing
526 // against the epoch we last actually sent for it.
527 bool phaseBiasFresh[CLOCKORBIT_SATNUM];
528 for (unsigned s = 0; s < CLOCKORBIT_SATNUM; s++) {
529 phaseBiasFresh[s] = _phaseBias.NumberOfSat[s] > 0 &&
530 _phaseBias.EpochTime[s] != _phaseBiasSentEpoch[s];
531 if (phaseBiasFresh[s]) {
532 _phaseBiasSentEpoch[s] = _phaseBias.EpochTime[s];
533 }
534 }
535 for (unsigned ii = 0; ii < CLOCKORBIT_NUMGPS
536 + CLOCKORBIT_NUMGLONASS
537 + CLOCKORBIT_NUMGALILEO
538 + CLOCKORBIT_NUMQZSS
539 + CLOCKORBIT_NUMSBAS
540 + _phaseBias.NumberOfSat[CLOCKORBIT_SATBDS];
541 ii++) {
542 char sys = ' ';
543 int num = _phaseBias.Sat[ii].ID;
544 int flag = 0;
545 if (ii < _phaseBias.NumberOfSat[CLOCKORBIT_SATGPS]) {
546 if (!phaseBiasFresh[CLOCKORBIT_SATGPS]) continue;
547 sys = 'G';
548 flag = t_eph::LNAV;
549 }
550 else if (ii >= CLOCKORBIT_OFFSETGLONASS &&
551 ii < CLOCKORBIT_OFFSETGLONASS + _phaseBias.NumberOfSat[CLOCKORBIT_SATGLONASS]) {
552 if (!phaseBiasFresh[CLOCKORBIT_SATGLONASS]) continue;
553 sys = 'R';
554 flag = t_eph::FDMA_M;
555 }
556 else if (ii >= CLOCKORBIT_OFFSETGALILEO &&
557 ii < CLOCKORBIT_OFFSETGALILEO + _phaseBias.NumberOfSat[CLOCKORBIT_SATGALILEO]) {
558 if (!phaseBiasFresh[CLOCKORBIT_SATGALILEO]) continue;
559 sys = 'E';
560 flag = t_eph::INAV;
561 }
562 else if (ii >= CLOCKORBIT_OFFSETQZSS &&
563 ii < CLOCKORBIT_OFFSETQZSS + _phaseBias.NumberOfSat[CLOCKORBIT_SATQZSS]) {
564 if (!phaseBiasFresh[CLOCKORBIT_SATQZSS]) continue;
565 sys = 'J';
566 flag = t_eph::LNAV;
567 }
568 else if (ii >= CLOCKORBIT_OFFSETSBAS &&
569 ii < CLOCKORBIT_OFFSETSBAS + _phaseBias.NumberOfSat[CLOCKORBIT_SATSBAS]) {
570 if (!phaseBiasFresh[CLOCKORBIT_SATSBAS]) continue;
571 sys = 'S';
572 flag = t_eph::SBASL1;
573 }
574 else if (ii >= CLOCKORBIT_OFFSETBDS &&
575 ii < CLOCKORBIT_OFFSETBDS + _phaseBias.NumberOfSat[CLOCKORBIT_SATBDS]) {
576 if (!phaseBiasFresh[CLOCKORBIT_SATBDS]) continue;
577 sys = 'C';
578 if (num < 6) {// GEO
579 flag = t_eph::D2;
580 }
581 else if (num > 58 && num < 63) { // GEO
582 flag = t_eph::D2;
583 }
584 else {
585 flag = t_eph::D1;
586 }
587 }
588 else {
589 continue;
590 }
591 t_satPhaseBias satPhaseBias;
592 satPhaseBias._prn.set(sys, num, flag);
593 checkPrnRange(satPhaseBias._prn);
594 satPhaseBias._staID = _staID.toStdString();
595 satPhaseBias._time = _lastTime;
596 satPhaseBias._updateInt = _phaseBias.UpdateInterval;
597 satPhaseBias._ssrIOD = _phaseBias.SSRIOD;
598 satPhaseBias._ssrProviderID = _phaseBias.SSRProviderID;
599 satPhaseBias._ssrFormat = (_type == RTCMssr) ? ssrRtcmOld :
600 (_type == RTCMnewssr) ? ssrRtcmNew : ssrUnknown;
601 if (satPhaseBias._ssrFormat == e_ssrFormat::ssrRtcmNew) {
602 satPhaseBias._satYawInfoInd = _phaseBias.SatelliteYawInformationIndicator;
603 satPhaseBias._extPBPhaseInd = _phaseBias.ExtendedPhaseBiasPropertyID;
604 }
605 else {
606 satPhaseBias._dispBiasConsistInd = _phaseBias.DispersiveBiasConsistencyIndicator;
607 satPhaseBias._MelbWuebConsistInd = _phaseBias.MWConsistencyIndicator;
608 }
609 satPhaseBias._yaw = _phaseBias.Sat[ii].YawAngle;
610 satPhaseBias._yawRate = _phaseBias.Sat[ii].YawRate;
611 for (unsigned jj = 0; jj < _phaseBias.Sat[ii].NumberOfPhaseBiases; jj++) {
612 const SsrCorr::PhaseBias::PhaseBiasSat::PhaseBiasEntry& biasEntry = _phaseBias.Sat[ii].Biases[jj];
613 t_frqPhaseBias frqPhaseBias;
614 frqPhaseBias._rnxType2ch.assign(_ssrCorr->codeTypeToRnxType(sys, biasEntry.Type));
615 frqPhaseBias._value = biasEntry.Bias;
616 frqPhaseBias._fixIndicator = biasEntry.IntegerIndicator;
617 if (satPhaseBias._ssrFormat == e_ssrFormat::ssrRtcmNew) {
618 if (satPhaseBias._extPBPhaseInd) {
619 frqPhaseBias._fixWideLaneIndicator = biasEntry.WidelaneGroupIndicator;
620 }
621 }
622 else {
623 frqPhaseBias._fixWideLaneIndicator = biasEntry.WidelaneGroupIndicator;
624 }
625 frqPhaseBias._jumpCounter = biasEntry.DiscontinuityCounter;
626 if (!frqPhaseBias._rnxType2ch.empty()) {
627 satPhaseBias._bias.push_back(frqPhaseBias);
628 }
629 }
630 _phaseBiases[_lastTime].append(satPhaseBias);
631 }
632
633 // Ionospheric Model
634 // -----------------
635 if (_vTEC.NumLayers > 0) {
636 _vTecMap[_lastTime]._time = _lastTime;
637 _vTecMap[_lastTime]._updateInt = _vTEC.UpdateInterval;
638 _vTecMap[_lastTime]._staID = _staID.toStdString();
639 for (unsigned ii = 0; ii < _vTEC.NumLayers; ii++) {
640 const SsrCorr::VTEC::IonoLayers& ionoLayer = _vTEC.Layers[ii];
641 t_vTecLayer layer;
642 layer._height = ionoLayer.Height;
643 layer._C.ReSize(ionoLayer.Degree+1, ionoLayer.Order+1);
644 layer._S.ReSize(ionoLayer.Degree+1, ionoLayer.Order+1);
645 for (unsigned iDeg = 0; iDeg <= ionoLayer.Degree; iDeg++) {
646 for (unsigned iOrd = 0; iOrd <= ionoLayer.Order; iOrd++) {
647 layer._C[iDeg][iOrd] = ionoLayer.Cosinus[iDeg][iOrd];
648 layer._S[iDeg][iOrd] = ionoLayer.Sinus[iDeg][iOrd];
649 }
650 }
651 _vTecMap[_lastTime]._layers.push_back(layer);
652 }
653 }
654
655 // Metadata (model correction information)
656 // ----------------------------------------
657 if (_metaData.NumEntries > 0) {
658 // Metadata carries no epoch field of its own (it is keyed here by
659 // _lastTime, inherited from whichever other message last set it), so a
660 // fresh, complete message must replace rather than accumulate onto
661 // whatever a previous message at the same _lastTime already stored.
662 _metaDataMap[_lastTime] = t_metaData();
663 _metaDataMap[_lastTime]._time = _lastTime;
664 _metaDataMap[_lastTime]._staID = _staID.toStdString();
665 _metaDataMap[_lastTime]._ssrIOD = _metaData.SSRIOD;
666 _metaDataMap[_lastTime]._providerID = _metaData.SSRProviderID;
667 _metaDataMap[_lastTime]._solutionID = _metaData.SSRSolutionID;
668 for (unsigned ii = 0; ii < _metaData.NumEntries; ii++) {
669 const SsrCorr::MetaData::ModelPart& part = _metaData.Entries[ii];
670 t_metaDataEntry entry;
671 entry._typeIndicator = part.TypeIndicator;
672 entry._applicationIndicator = part.ApplicationIndicator;
673 entry._nonDefaultIndicator = part.nonDefaultIndicator;
674 entry._nonDefaultIdentifier = part.nonDefaultIdentifier;
675 entry._dataIODIndicator = part.DataIODIndicator;
676 entry._dataIOD = part.DataIOD;
677 _metaDataMap[_lastTime]._entries.push_back(entry);
678 }
679 }
680
681 // Satellite Antenna corrections
682 // ------------------------------
683 // _antenna, like _metaData, carries no epoch field of its own and is never
684 // wiped by reset(), so each system's data persists across unrelated
685 // messages. Only a system for which an Antenna message was actually
686 // received (_antennaReceived, latched in Decode()) is considered, so
687 // persisted data is never re-stamped with a newer time.
688 //
689 // The SatelliteAntennaIOD is unique only per SSR Provider ID (DF414) and
690 // only within 64 days, and a client that has not received the stream for
691 // 64 days must discard cached antenna data (enforced downstream against
692 // t_satAntenna::_time, see ssrSatAntennaTrusted() in pppSatObs.cpp). So an
693 // unchanged (provider ID, IOD) retransmission is suppressed only if it was
694 // last emitted less than ANT_REFRESH_SEC ago. Re-emitting it after that
695 // keeps _time close to the last reception - not the first - so continuously
696 // received data never ages out. It also means a reception after a long
697 // gap is always emitted, even when the provider has legitimately reused
698 // the IOD for different content, and each .ssr file contains the antenna
699 // data at least once per refresh interval.
700 //
701 // This is tracked per satellite, not per system: providers may split a
702 // system's satellites across several messages with rotating satellite
703 // masks (DF394), all carrying the same IOD - a per-system check would
704 // emit only whichever subset happened to arrive first.
705 {
706 const double ANT_REFRESH_SEC = 3600.0;
707 struct SysInfo { unsigned sysIdx; char sysChar; unsigned numSat; unsigned offset; };
708 const SysInfo sysInfos[] = {
709 { CLOCKORBIT_SATGPS, 'G', CLOCKORBIT_NUMGPS, CLOCKORBIT_OFFSETGPS },
710 { CLOCKORBIT_SATGLONASS, 'R', CLOCKORBIT_NUMGLONASS, CLOCKORBIT_OFFSETGLONASS },
711 { CLOCKORBIT_SATGALILEO, 'E', CLOCKORBIT_NUMGALILEO, CLOCKORBIT_OFFSETGALILEO },
712 { CLOCKORBIT_SATQZSS, 'J', CLOCKORBIT_NUMQZSS, CLOCKORBIT_OFFSETQZSS },
713 { CLOCKORBIT_SATBDS, 'C', CLOCKORBIT_NUMBDS, CLOCKORBIT_OFFSETBDS },
714 };
715 for (const SysInfo& si : sysInfos) {
716 unsigned s = si.sysIdx;
717 if (!_antennaReceived[s]) {
718 continue; // no Antenna message received for this system since the last check
719 }
720 _antennaReceived[s] = false;
721 if (_antenna.SatelliteMask[s] == 0) {
722 continue; // no satellites in this system's antenna data
723 }
724 for (unsigned k = 0; k < si.numSat; k++) {
725 if (!((_antenna.SatelliteMask[s] >> (63 - k)) & 1ULL)) {
726 continue;
727 }
728 unsigned i = si.offset + k;
729 if (_antennaSentTime[i].valid() &&
730 _antenna.SSRProviderID[s] == _antennaSentProviderID[i] &&
731 _antenna.SatelliteAntennaIOD[s] == _antennaSentIOD[i] &&
732 _lastTime - _antennaSentTime[i] < ANT_REFRESH_SEC) {
733 continue; // unchanged retransmission, emitted recently
734 }
735 _antennaSentProviderID[i] = _antenna.SSRProviderID[s];
736 _antennaSentIOD[i] = _antenna.SatelliteAntennaIOD[s];
737 _antennaSentTime[i] = _lastTime;
738 int num = k + 1; // PRN within this system, DF394 MSB-first convention
739 int flag = 0;
740 switch (si.sysChar) {
741 case 'G': flag = t_eph::LNAV; break;
742 case 'R': flag = t_eph::FDMA_M; break;
743 case 'E': flag = t_eph::INAV; break;
744 case 'J': flag = t_eph::LNAV; break;
745 case 'C': flag = (num < 6 || (num > 58 && num < 63)) ? t_eph::D2 : t_eph::D1; break;
746 }
747
748 t_satAntenna satAntenna;
749 satAntenna._prn.set(si.sysChar, num, flag);
750 satAntenna._staID = _staID.toStdString();
751 satAntenna._time = _lastTime;
752 satAntenna._ssrProviderID = _antenna.SSRProviderID[s];
753 satAntenna._satelliteAntennaIOD = _antenna.SatelliteAntennaIOD[s];
754 satAntenna._phaseCenterInfoInd = _antenna.PhaseCenterInformationIndicator[s];
755 satAntenna._groupDelayInfoInd = _antenna.GroupDelayInformationIndicator[s];
756 satAntenna._nadirAngleDependentCorrInd = _antenna.NadirAngleDependentCorrectionIndicator[s];
757 satAntenna._maximumOffNadirAngle = _antenna.maximumOffNadirAngle[s];
758 satAntenna._nadirAngleRangeExtension = _antenna.NadirAngleDependentCorrectionRangeExtension[s];
759
760 const SsrCorr::Antenna::SatellitePart& satPart = _antenna.Sat[i];
761 for (unsigned f = 0; f < ANT_MAXFREQUENCIES; f++) {
762 if (!((satPart.GnssFrequencyMask >> (ANT_MAXFREQUENCIES - 1 - f)) & 1U)) {
763 continue;
764 }
765 t_frqAntenna frqAntenna;
766 frqAntenna._frqType = _ssrCorr->antFreqIndexToStr(si.sysChar, f);
767 if (frqAntenna._frqType.empty()) {
768 continue; // reserved/unmapped DF+018 bit position
769 }
770 frqAntenna._nadirCorrectionIndicator = satPart.Freq[f].NadirCorrectionIndicator;
771 frqAntenna._nadirCorrection = satPart.Freq[f].NadirCorrection;
772 if (satAntenna._nadirAngleDependentCorrInd) {
773 unsigned count = satAntenna._maximumOffNadirAngle + 1;
774 for (unsigned deg = 0; deg < count && deg < ANT_MAXNADIRDEGREES; deg++) {
775 frqAntenna._nadirAngleCorrection.push_back(satPart.Freq[f].NadirAngleCorrection[deg]);
776 }
777 }
778 satAntenna._freq.push_back(frqAntenna);
779 }
780 _satAntennas[_lastTime].append(satAntenna);
781 }
782 }
783 }
784
785 // Dump all older epochs
786 // ---------------------
787 dumpEpochs(false);
788}
789
790// Emit and write the collected corrections of all epochs older than
791// _lastTime (all epochs if all is set, e.g. at the end of a replay)
792////////////////////////////////////////////////////////////////////////////
793void RTCM3coDecoder::dumpEpochs(bool all) {
794 QMutableMapIterator<bncTime, QList<t_orbCorr> > itOrb(_orbCorrections);
795 while (itOrb.hasNext()) {
796 itOrb.next();
797 if (all || itOrb.key() < _lastTime) {
798 emit newOrbCorrections(itOrb.value());
799 t_orbCorr::writeEpoch(_out, itOrb.value());
800 itOrb.remove();
801 }
802 }
803 QMutableMapIterator<bncTime, QList<t_clkCorr> > itClk(_clkCorrections);
804 while (itClk.hasNext()) {
805 itClk.next();
806 if (all || itClk.key() < _lastTime) {
807 emit newClkCorrections(itClk.value());
808 t_clkCorr::writeEpoch(_out, itClk.value());
809 itClk.remove();
810 }
811 }
812 QMutableMapIterator<bncTime, QList<t_satCodeBias> > itCB(_codeBiases);
813 while (itCB.hasNext()) {
814 itCB.next();
815 if (all || itCB.key() < _lastTime) {
816 emit newCodeBiases(itCB.value());
817 t_satCodeBias::writeEpoch(_out, itCB.value());
818 itCB.remove();
819 }
820 }
821 QMutableMapIterator<bncTime, QList<t_satPhaseBias> > itPB(_phaseBiases);
822 while (itPB.hasNext()) {
823 itPB.next();
824 if (all || itPB.key() < _lastTime) {
825 emit newPhaseBiases(itPB.value());
826 t_satPhaseBias::writeEpoch(_out, itPB.value());
827 itPB.remove();
828 }
829 }
830 QMutableMapIterator<bncTime, t_vTec> itTec(_vTecMap);
831 while (itTec.hasNext()) {
832 itTec.next();
833 if (all || itTec.key() < _lastTime) {
834 emit newTec(itTec.value());
835 t_vTec::write(_out, itTec.value());
836 itTec.remove();
837 }
838 }
839 QMutableMapIterator<bncTime, t_metaData> itMD(_metaDataMap);
840 while (itMD.hasNext()) {
841 itMD.next();
842 if (all || itMD.key() < _lastTime) {
843 emit newMetaData(itMD.value());
844 t_metaData::write(_out, itMD.value());
845 itMD.remove();
846 }
847 }
848 QMutableMapIterator<bncTime, QList<t_satAntenna> > itAnt(_satAntennas);
849 while (itAnt.hasNext()) {
850 itAnt.next();
851 if (all || itAnt.key() < _lastTime) {
852 emit newSatAntennas(itAnt.value());
853 t_satAntenna::writeEpoch(_out, itAnt.value());
854 itAnt.remove();
855 }
856 }
857 if (all && _out) {
858 _out->flush();
859 }
860}
861
862//
863////////////////////////////////////////////////////////////////////////////
864void RTCM3coDecoder::checkProviderID() {
865
866 if (_clkOrb.SSRProviderID == 0 && _clkOrb.SSRSolutionID == 0 && _clkOrb.SSRIOD == 0) {
867 return;
868 }
869
870 int newProviderID[3];
871 newProviderID[0] = _clkOrb.SSRProviderID;
872 newProviderID[1] = _clkOrb.SSRSolutionID;
873 newProviderID[2] = _clkOrb.SSRIOD;
874 QString newProviderIDStr = QString(" [SSR Provider ID: %1 SSR Solution ID: %2 SSR IOD: %3]: ")
875 .arg(newProviderID[0]).arg(newProviderID[1]).arg(newProviderID[2]);
876
877 bool alreadySet = false;
878 bool different = false;
879
880 for (unsigned ii = 0; ii < 3; ii++) {
881 if (_providerID[ii] != -1) {
882 alreadySet = true;
883 }
884 if (_providerID[ii] != newProviderID[ii]) {
885 different = true;
886 }
887 _providerID[ii] = newProviderID[ii];
888 }
889
890 if (alreadySet && different) {
891 emit newMessage("RTCM3coDecoder: SSR Provider or Service changed " + newProviderIDStr.toLatin1() + _staID.toLatin1(), true);
892 emit providerIDChanged(_staID);
893 }
894}
895
896// Check corrections
897////////////////////////////////////////////////////////////////////////////
898bool RTCM3coDecoder::corrIsOutOfRange(const SsrCorr::ClockOrbit::SatData& coSat) {
899
900 QString ssrParStr;
901 QString ssrParValue;
902 bool corrIsOutOfRange = false;
903
904 switch (_type) {
905 // ======== //
906 // IGS SSR //
907 // ======== //
908 case IGSssr:
909 if (coSat.Clock.DeltaA0 < -209.7151 ||
910 coSat.Clock.DeltaA0 > +209.7151) {
911 ssrParStr = "Clock::DeltaA0";
912 ssrParValue = QString::number(coSat.Clock.DeltaA0, 'f', 4);
913 corrIsOutOfRange = true;
914 }
915 if (coSat.Clock.DeltaA1 < -1.048575 ||
916 coSat.Clock.DeltaA1 > +1.048575) {
917 ssrParStr = "Clock::DeltaA1";
918 ssrParValue = QString::number(coSat.Clock.DeltaA1, 'f', 6);
919 corrIsOutOfRange = true;
920 }
921 if (coSat.Clock.DeltaA2 < -1.3421772 ||
922 coSat.Clock.DeltaA2 > +1.3421772) {
923 ssrParStr = "Clock::DeltaA2";
924 ssrParValue = QString::number(coSat.Clock.DeltaA2, 'f', 7);
925 corrIsOutOfRange = true;
926 }
927
928 if (coSat.Orbit.DeltaRadial < -209.7151 ||
929 coSat.Orbit.DeltaRadial > +209.7151) {
930 ssrParStr = "Orbit::DeltaRadial";
931 ssrParValue = QString::number(coSat.Orbit.DeltaRadial, 'f', 4);
932 corrIsOutOfRange = true;
933 }
934
935 if (coSat.Orbit.DeltaAlongTrack < -209.7148 ||
936 coSat.Orbit.DeltaAlongTrack > +209.7148) {
937 ssrParStr = "Orbit::DeltaAlongTrack";
938 ssrParValue = QString::number(coSat.Orbit.DeltaAlongTrack, 'f', 4);
939 corrIsOutOfRange = true;
940 }
941 if (coSat.Orbit.DeltaCrossTrack < -209.7148 ||
942 coSat.Orbit.DeltaCrossTrack > +209.7148) {
943 ssrParStr = "Orbit::DeltaCrossTrack";
944 ssrParValue = QString::number(coSat.Orbit.DeltaCrossTrack, 'f', 4);
945 corrIsOutOfRange = true;
946 }
947
948 if (coSat.Orbit.DotDeltaRadial < -1.048575 ||
949 coSat.Orbit.DotDeltaRadial > +1.048575) {
950 ssrParStr = "Orbit::DotDeltaRadial";
951 ssrParValue = QString::number(coSat.Orbit.DotDeltaRadial, 'f', 6);
952 corrIsOutOfRange = true;
953 }
954 if (coSat.Orbit.DotDeltaAlongTrack < -1.048572 ||
955 coSat.Orbit.DotDeltaAlongTrack > +1.048572) {
956 ssrParStr = "Orbit::DotDeltaAlongTrack";
957 ssrParValue = QString::number(coSat.Orbit.DotDeltaAlongTrack, 'f', 6);
958 corrIsOutOfRange = true;
959 }
960 if (coSat.Orbit.DotDeltaCrossTrack < -1.048572 ||
961 coSat.Orbit.DotDeltaCrossTrack > +1.048572) {
962 ssrParStr = "Orbit::DotDeltaCrossTrack";
963 ssrParValue = QString::number(coSat.Orbit.DotDeltaCrossTrack, 'f', 6);
964 corrIsOutOfRange = true;
965 }
966 break;
967 //==========//
968 // RTCM SSR //
969 // =========//
970 case RTCMssr:
971 case RTCMnewssr:
972 if (coSat.Clock.DeltaA0 < -209.7151 ||
973 coSat.Clock.DeltaA0 > +209.7151) {
974 ssrParStr = "Clock::DeltaA0";
975 ssrParValue = QString::number(coSat.Clock.DeltaA0, 'f', 4);
976 corrIsOutOfRange = true;
977 }
978 if (coSat.Clock.DeltaA1 < -1.048575 ||
979 coSat.Clock.DeltaA1 > +1.048575) {
980 ssrParStr = "Clock::DeltaA1";
981 ssrParValue = QString::number(coSat.Clock.DeltaA1, 'f', 6);
982 corrIsOutOfRange = true;
983 }
984 if (coSat.Clock.DeltaA2 < -1.34217726 ||
985 coSat.Clock.DeltaA2 > +1.34217726) {
986 ssrParStr = "Clock::DeltaA2";
987 ssrParValue = QString::number(coSat.Clock.DeltaA2, 'f', 8);
988 corrIsOutOfRange = true;
989 }
990
991 if (coSat.Orbit.DeltaRadial < -209.7151 ||
992 coSat.Orbit.DeltaRadial > +209.7151) {
993 ssrParStr = "Orbit::DeltaRadial";
994 ssrParValue = QString::number(coSat.Orbit.DeltaRadial, 'f', 4);
995 corrIsOutOfRange = true;
996 }
997
998 if (coSat.Orbit.DeltaAlongTrack < -209.7148 ||
999 coSat.Orbit.DeltaAlongTrack > +209.7148) {
1000 ssrParStr = "Orbit::DeltaAlongTrack";
1001 ssrParValue = QString::number(coSat.Orbit.DeltaAlongTrack, 'f', 4);
1002 corrIsOutOfRange = true;
1003 }
1004 if (coSat.Orbit.DeltaCrossTrack < -209.7148 ||
1005 coSat.Orbit.DeltaCrossTrack > +209.7148) {
1006 ssrParStr = "Orbit::DeltaCrossTrack";
1007 ssrParValue = QString::number(coSat.Orbit.DeltaCrossTrack, 'f', 4);
1008 corrIsOutOfRange = true;
1009 }
1010
1011 if (coSat.Orbit.DotDeltaRadial < -1.048575 ||
1012 coSat.Orbit.DotDeltaRadial > +1.048575) {
1013 ssrParStr = "Orbit::DotDeltaRadial";
1014 ssrParValue = QString::number(coSat.Orbit.DotDeltaRadial, 'f', 6);
1015 corrIsOutOfRange = true;
1016 }
1017 if (coSat.Orbit.DotDeltaAlongTrack < -1.048572 ||
1018 coSat.Orbit.DotDeltaAlongTrack > +1.048572) {
1019 ssrParStr = "Orbit::DotDeltaAlongTrack";
1020 ssrParValue = QString::number(coSat.Orbit.DotDeltaAlongTrack, 'f', 6);
1021 corrIsOutOfRange = true;
1022 }
1023 if (coSat.Orbit.DotDeltaCrossTrack < -1.048572 ||
1024 coSat.Orbit.DotDeltaCrossTrack > +1.048572) {
1025 ssrParStr = "Orbit::DotDeltaCrossTrack";
1026 ssrParValue = QString::number(coSat.Orbit.DotDeltaCrossTrack, 'f', 6);
1027 corrIsOutOfRange = true;
1028 }
1029 break;
1030 }
1031
1032 if (corrIsOutOfRange) {
1033 emit newMessage("RTCM3coDecoder: Correction " + ssrParStr.toLatin1()
1034 + " (" + ssrParValue.toLatin1() + ") "
1035 + "is out of range " + _staID.toLatin1(), true);
1036 }
1037
1038 return corrIsOutOfRange;
1039}
1040
1041//
1042////////////////////////////////////////////////////////////////////////////
1043void RTCM3coDecoder::setEpochTime() {
1044
1045 // _phaseBias is no longer wiped between messages (kept alive so a later
1046 // Extended Phase Bias message can correlate against it), so
1047 // NumberOfSat[s] > 0 alone no longer means "phase bias was just decoded
1048 // this cycle" - it can be stale from many messages ago. Gate each
1049 // phase-bias fallback below on the same per-system freshness tracking
1050 // sendResults() uses (_phaseBiasSentEpoch), so a stale leftover value
1051 // never gets picked as this cycle's epoch.
1052 bncTime prevTime = _lastTime;
1053 _lastTime.reset();
1054
1055 double epoSecGPS = -1.0;
1056 double epoSecGlo = -1.0;
1057 double epoSecGal = -1.0;
1058 double epoSecQzss = -1.0;
1059 double epoSecSbas = -1.0;
1060 double epoSecBds = -1.0;
1061 if (_clkOrb.NumberOfSat[CLOCKORBIT_SATGPS] > 0) {
1062 epoSecGPS = _clkOrb.EpochTime[CLOCKORBIT_SATGPS]; // 0 .. 604799 s
1063 }
1064 else if (_codeBias.NumberOfSat[CLOCKORBIT_SATGPS] > 0) {
1065 epoSecGPS = _codeBias.EpochTime[CLOCKORBIT_SATGPS]; // 0 .. 604799 s
1066 }
1067 else if (_phaseBias.NumberOfSat[CLOCKORBIT_SATGPS] > 0 &&
1068 _phaseBias.EpochTime[CLOCKORBIT_SATGPS] != _phaseBiasSentEpoch[CLOCKORBIT_SATGPS]) {
1069 epoSecGPS = _phaseBias.EpochTime[CLOCKORBIT_SATGPS]; // 0 .. 604799 s
1070 }
1071 else if (_vTEC.NumLayers > 0) {
1072 epoSecGPS = _vTEC.EpochTime; // 0 .. 604799 s
1073 }
1074 else if (_clkOrb.NumberOfSat[CLOCKORBIT_SATGLONASS] > 0) {
1075 epoSecGlo = _clkOrb.EpochTime[CLOCKORBIT_SATGLONASS]; // 0 .. 86399 s
1076 }
1077 else if (_codeBias.NumberOfSat[CLOCKORBIT_SATGLONASS] > 0) {
1078 epoSecGlo = _codeBias.EpochTime[CLOCKORBIT_SATGLONASS]; // 0 .. 86399 s
1079 }
1080 else if (_phaseBias.NumberOfSat[CLOCKORBIT_SATGLONASS] > 0 &&
1081 _phaseBias.EpochTime[CLOCKORBIT_SATGLONASS] != _phaseBiasSentEpoch[CLOCKORBIT_SATGLONASS]) {
1082 epoSecGlo = _phaseBias.EpochTime[CLOCKORBIT_SATGLONASS]; // 0 .. 86399 s
1083 }
1084 else if (_clkOrb.NumberOfSat[CLOCKORBIT_SATGALILEO] > 0) {
1085 epoSecGal = _clkOrb.EpochTime[CLOCKORBIT_SATGALILEO];
1086 }
1087 else if (_codeBias.NumberOfSat[CLOCKORBIT_SATGALILEO] > 0) {
1088 epoSecGal = _codeBias.EpochTime[CLOCKORBIT_SATGALILEO];
1089 }
1090 else if (_phaseBias.NumberOfSat[CLOCKORBIT_SATGALILEO] > 0 &&
1091 _phaseBias.EpochTime[CLOCKORBIT_SATGALILEO] != _phaseBiasSentEpoch[CLOCKORBIT_SATGALILEO]) {
1092 epoSecGal = _phaseBias.EpochTime[CLOCKORBIT_SATGALILEO];
1093 }
1094 else if (_clkOrb.NumberOfSat[CLOCKORBIT_SATQZSS] > 0) {
1095 epoSecQzss = _clkOrb.EpochTime[CLOCKORBIT_SATQZSS];
1096 }
1097 else if (_codeBias.NumberOfSat[CLOCKORBIT_SATQZSS] > 0) {
1098 epoSecQzss = _codeBias.EpochTime[CLOCKORBIT_SATQZSS];
1099 }
1100 else if (_phaseBias.NumberOfSat[CLOCKORBIT_SATQZSS] > 0 &&
1101 _phaseBias.EpochTime[CLOCKORBIT_SATQZSS] != _phaseBiasSentEpoch[CLOCKORBIT_SATQZSS]) {
1102 epoSecQzss = _phaseBias.EpochTime[CLOCKORBIT_SATQZSS];
1103 }
1104 else if (_clkOrb.NumberOfSat[CLOCKORBIT_SATSBAS] > 0) {
1105 epoSecSbas = _clkOrb.EpochTime[CLOCKORBIT_SATSBAS];
1106 }
1107 else if (_codeBias.NumberOfSat[CLOCKORBIT_SATSBAS] > 0) {
1108 epoSecSbas = _codeBias.EpochTime[CLOCKORBIT_SATSBAS];
1109 }
1110 else if (_phaseBias.NumberOfSat[CLOCKORBIT_SATSBAS] > 0 &&
1111 _phaseBias.EpochTime[CLOCKORBIT_SATSBAS] != _phaseBiasSentEpoch[CLOCKORBIT_SATSBAS]) {
1112 epoSecSbas = _phaseBias.EpochTime[CLOCKORBIT_SATSBAS];
1113 }
1114 else if (_clkOrb.NumberOfSat[CLOCKORBIT_SATBDS] > 0) {
1115 epoSecBds = _clkOrb.EpochTime[CLOCKORBIT_SATBDS];
1116 }
1117 else if (_codeBias.NumberOfSat[CLOCKORBIT_SATBDS] > 0) {
1118 epoSecBds = _codeBias.EpochTime[CLOCKORBIT_SATBDS];
1119 }
1120 else if (_phaseBias.NumberOfSat[CLOCKORBIT_SATBDS] > 0 &&
1121 _phaseBias.EpochTime[CLOCKORBIT_SATBDS] != _phaseBiasSentEpoch[CLOCKORBIT_SATBDS]) {
1122 epoSecBds = _phaseBias.EpochTime[CLOCKORBIT_SATBDS];
1123 }
1124
1125 // Retrieve current time
1126 // ---------------------
1127 int currentWeek = 0;
1128 double currentSec = 0.0;
1129 currentGPSWeeks(currentWeek, currentSec);
1130 bncTime currentTime(currentWeek, currentSec);
1131
1132 // Set _lastTime close to currentTime
1133 // ----------------------------------
1134 if (_type == IGSssr) {
1135 if (epoSecGPS != -1) _igsRefEpoSec = epoSecGPS;
1136 else if (epoSecGal != -1) _igsRefEpoSec = epoSecGal;
1137 else if (epoSecQzss != -1) _igsRefEpoSec = epoSecQzss;
1138 else if (epoSecSbas != -1) _igsRefEpoSec = epoSecSbas;
1139 }
1140
1141 if (epoSecGPS != -1) {
1142 _lastTime.set(currentWeek, epoSecGPS);
1143 }
1144 else if (epoSecGlo != -1) {
1145 QDate date = dateAndTimeFromGPSweek(currentTime.gpsw(), currentTime.gpssec()).date();
1146 double leap = gnumleap(date.year(), date.month(), date.day());
1147 // IGS-SSR: the SSR Epoch Time (IDF003, 20 bit) is given in one continuous
1148 // time scale for all GNSS, i.e. GPS seconds of week also for GLONASS
1149 // (IGS SSR v1.00) - no conversion. Old encoders (e.g. BNC before
1150 // 2026-09-30) wrote UTC seconds of day instead: recognised by comparison
1151 // with the GPS/Galileo epoch of the same stream and converted, with a
1152 // warning, during a transition period.
1153 if (_type == IGSssr && _igsRefEpoSec >= 0.0 &&
1154 igsLegacyEpoch(epoSecGlo, fmod(_igsRefEpoSec - leap + 7 * 86400.0, 86400.0), 86400.0)) {
1155 double sow = floor(_igsRefEpoSec / 86400.0) * 86400.0 + epoSecGlo + leap;
1156 if (sow - _igsRefEpoSec > 43200.0) sow -= 86400.0;
1157 else if (_igsRefEpoSec - sow > 43200.0) sow += 86400.0;
1158 epoSecGlo = sow;
1159 bncTime& warned = _legacyEpochWarned['R'];
1160 if (!warned.valid() || currentTime - warned >= 3600.0) {
1161 warned = currentTime;
1162 emit newMessage(_staID.toLatin1() + ": IGS-SSR GLONASS epoch time in outdated convention "
1163 "(UTC seconds of day instead of GPS seconds of week, IGS SSR v1.00 IDF003) "
1164 "detected and converted - the SSR encoder should be updated", true);
1165 }
1166 }
1167 if (_type == RTCMssr || _type == RTCMnewssr) {
1168 // Same GLONASS epoch wire convention (UTC+3h) as RTCMssr
1169 epoSecGlo = epoSecGlo - 3 * 3600 + leap;
1170 }
1171 _lastTime.set(currentWeek, epoSecGlo);
1172 }
1173 else if (epoSecGal != -1) {
1174 _lastTime.set(currentWeek, epoSecGal);
1175 }
1176 else if (epoSecQzss != -1) {
1177 _lastTime.set(currentWeek, epoSecQzss);
1178 }
1179 else if (epoSecSbas != -1) {
1180 _lastTime.set(currentWeek, epoSecSbas);
1181 }
1182 else if (epoSecBds != -1) {
1183 // RTCM-SSR: BDT; IGS-SSR: GPS time scale for all GNSS (IDF003). Old
1184 // IGS-SSR encoders wrote BDT: recognised and converted as for GLONASS.
1185 bool bdt = (_type != IGSssr);
1186 if (_type == IGSssr && _igsRefEpoSec >= 0.0 &&
1187 igsLegacyEpoch(epoSecBds, fmod(_igsRefEpoSec - 14.0 + 604800.0, 604800.0), 604800.0)) {
1188 bdt = true;
1189 bncTime& warned = _legacyEpochWarned['C'];
1190 if (!warned.valid() || currentTime - warned >= 3600.0) {
1191 warned = currentTime;
1192 emit newMessage(_staID.toLatin1() + ": IGS-SSR BDS epoch time in outdated convention "
1193 "(BDT instead of GPS seconds of week, IGS SSR v1.00 IDF003) "
1194 "detected and converted - the SSR encoder should be updated", true);
1195 }
1196 }
1197 if (bdt) {
1198 epoSecBds += 14.0;
1199 if (epoSecBds > 604800.0) {
1200 epoSecBds -= 7.0*24.0*60.0*60.0;
1201 }
1202 }
1203 _lastTime.set(currentWeek, epoSecBds);
1204 }
1205 // Some message types (e.g. Metadata) carry no epoch field of their own,
1206 // and none of the above found anything fresh to use either - fall back
1207 // to the last known valid time rather than leaving _lastTime invalid
1208 // (which would silently drop the message's output entirely).
1209 else if (prevTime.valid()) {
1210 _lastTime = prevTime;
1211 }
1212
1213 if (_lastTime.valid()) {
1214 double maxDiff = 12 * 3600.0;
1215 while (_lastTime < currentTime - maxDiff) {
1216 _lastTime = _lastTime + maxDiff;
1217 }
1218 while (_lastTime > currentTime + maxDiff) {
1219 _lastTime = _lastTime - maxDiff;
1220 }
1221 }
1222}
1223
1224// IGS-SSR: true if value (seconds, periodic with period) matches the epoch
1225// expected in the outdated convention (legacyRef) better than the GPS time
1226// reference of the stream, within the spread of epochs of one update cycle
1227////////////////////////////////////////////////////////////////////////////
1228bool RTCM3coDecoder::igsLegacyEpoch(double value, double legacyRef, double period) const {
1229 auto dist = [period](double a, double b) {
1230 double d = fmod(fabs(a - b), period);
1231 return std::min(d, period - d);
1232 };
1233 double dSpec = dist(value, fmod(_igsRefEpoSec, period));
1234 double dLegacy = dist(value, legacyRef);
1235 return dLegacy < dSpec && dLegacy <= 30.0;
1236}
1237
1238// Satellite numbers on the wire can exceed BNC's supported range (e.g. 6-bit
1239// IDs up to 63, while t_prn supports G01-G32). They are passed on unchanged,
1240// but t_prn::toInt() maps them to the unused index 0, so they are not usable
1241// for per-satellite processing - report that once per satellite.
1242////////////////////////////////////////////////////////////////////////////
1243void RTCM3coDecoder::checkPrnRange(const t_prn& prn) {
1244 if (prn.toInt() != 0) {
1245 return;
1246 }
1247 QString prnStr = QString::fromStdString(prn.toString());
1248 if (!_prnOutOfRangeLogged.contains(prnStr)) {
1249 _prnOutOfRangeLogged.insert(prnStr);
1250 emit newMessage(QString("%1: satellite %2 outside the range supported by BNC - not usable for processing")
1251 .arg(_staID).arg(prnStr).toLatin1(), true);
1252 }
1253}
1254
1255// All Satellite Antenna messages of one GNSS must use the same Satellite
1256// Antenna IOD (a satellite set may be split over several messages). During
1257// a legitimate IOD change old and new IOD are both seen for a while, until
1258// every message has been sent with the new one - but the IOD never returns
1259// to a replaced value within 64 days (uniqueness per SSR Provider ID), so a
1260// switch back to the replaced IOD within that period reveals messages of the
1261// same GNSS using different IODs. Reported once per GNSS and IOD pair; the
1262// data itself is not changed (PPP checks each satellite's IOD individually).
1263////////////////////////////////////////////////////////////////////////////
1264void RTCM3coDecoder::checkAntennaIOD(unsigned s) {
1265 const double MAX_AGE_SEC = 64.0 * 86400.0;
1266 unsigned int iod = _antenna.SatelliteAntennaIOD[s];
1267 unsigned int provider = _antenna.SSRProviderID[s];
1268
1269 int currentWeek = 0;
1270 double currentSec = 0.0;
1271 currentGPSWeeks(currentWeek, currentSec);
1272 bncTime currentTime(currentWeek, currentSec);
1273
1274 if (!_antennaIODKnown[s] || provider != _antennaIODProvider[s]) {
1275 _antennaIODKnown[s] = true;
1276 _antennaIODProvider[s] = provider;
1277 _antennaIODCur[s] = iod;
1278 _antennaIODPrevTime[s].reset(); // no replaced IOD yet
1279 return;
1280 }
1281 if (iod == _antennaIODCur[s]) {
1282 return;
1283 }
1284 if (_antennaIODPrevTime[s].valid() && iod == _antennaIODPrev[s] &&
1285 currentTime - _antennaIODPrevTime[s] < MAX_AGE_SEC) {
1286 const char sysChars[CLOCKORBIT_SATNUM] = {'G', 'R', 'E', 'J', 'S', 'C'};
1287 char sys = (s < CLOCKORBIT_SATNUM) ? sysChars[s] : '?';
1288 QString key = QString("%1_%2_%3").arg(sys).arg(_antennaIODCur[s]).arg(iod);
1289 if (!_antennaIODLogged.contains(key)) {
1290 _antennaIODLogged.insert(key);
1291 emit newMessage(QString("%1: GNSS %2 Satellite Antenna IOD switches back %3 -> %4"
1292 " - messages of one GNSS must use the same IOD")
1293 .arg(_staID).arg(sys).arg(_antennaIODCur[s]).arg(iod).toLatin1(), true);
1294 }
1295 }
1296 _antennaIODPrev[s] = _antennaIODCur[s];
1297 _antennaIODPrevTime[s] = currentTime;
1298 _antennaIODCur[s] = iod;
1299}
Note: See TracBrowser for help on using the repository browser.