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

Last change on this file since 11097 was 11097, checked in by stuerze, 23 hours ago

bug fixed

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