source: ntrip/trunk/BNC/src/latencychecker.cpp@ 10605

Last change on this file since 10605 was 10586, checked in by stuerze, 2 months ago

minor changes

File size: 18.3 KB
RevLine 
[1558]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://www.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: latencyChecker
30 *
31 * Purpose: Check incoming GNSS data for latencies, gaps etc.
32 *
33 * Author: G. Weber
34 *
35 * Created: 02-Feb-2009
36 *
[8011]37 * Changes:
[1558]38 *
39 * -----------------------------------------------------------------------*/
40
[1564]41#include <iostream>
[1578]42
[1577]43#ifdef WIN32
[9958]44#include <windows.h>
[1578]45#else
46#include <unistd.h>
[1577]47#endif
[1564]48
[1558]49#include "latencychecker.h"
[5070]50#include "bnccore.h"
[1558]51#include "bncutils.h"
52#include "bncsettings.h"
53
[1564]54using namespace std;
55
[1558]56// Constructor
57//////////////////////////////////////////////////////////////////////////////
58latencyChecker::latencyChecker(QByteArray staID) {
59
60 _staID = staID;
[10586]61 _curLat = 0.0;
[1558]62
[8011]63 connect(this, SIGNAL(newMessage(QByteArray,bool)),
[5068]64 BNC_CORE, SLOT(slotMessage(const QByteArray,bool)));
[1558]65
66 bncSettings settings;
67
68 // Notice threshold
69 // ----------------
[7333]70 QString adviseObsRate = settings.value("adviseObsRate").toString();
[1558]71 _inspSegm = 0;
[8011]72 if ( adviseObsRate.isEmpty() ) {
73 _inspSegm = 0;
[1558]74 }
[8011]75 else if ( adviseObsRate.indexOf("5 Hz") != -1 ) {
76 _inspSegm = 20;
[1558]77 }
[8011]78 else if ( adviseObsRate.indexOf("1 Hz") != -1 ) {
79 _inspSegm = 10;
[1558]80 }
[8011]81 else if ( adviseObsRate.indexOf("0.5 Hz") != -1 ) {
82 _inspSegm = 20;
[1558]83 }
[8011]84 else if ( adviseObsRate.indexOf("0.2 Hz") != -1 ) {
85 _inspSegm = 40;
[1558]86 }
[8011]87 else if ( adviseObsRate.indexOf("0.1 Hz") != -1 ) {
88 _inspSegm = 50;
[1558]89 }
90 _adviseFail = settings.value("adviseFail").toInt();
91 _adviseReco = settings.value("adviseReco").toInt();
92 _adviseScript = settings.value("adviseScript").toString();
93 expandEnvVar(_adviseScript);
94
95 // Latency interval/average
96 // ------------------------
[10517]97 _miscIntr = 300;
[7422]98 QString miscIntr = settings.value("miscIntr").toString();
[8011]99 if ( miscIntr.isEmpty() ) {
[10517]100 _miscIntr = 300;
[1558]101 }
[8011]102 else if ( miscIntr.indexOf("2 sec") != -1 ) {
103 _miscIntr = 2;
[1558]104 }
[8011]105 else if ( miscIntr.indexOf("10 sec") != -1 ) {
106 _miscIntr = 10;
[1558]107 }
[8011]108 else if ( miscIntr.indexOf("1 min") != -1 ) {
109 _miscIntr = 60;
[1558]110 }
[8011]111 else if ( miscIntr.left(5).indexOf("5 min") != -1 ) {
112 _miscIntr = 300;
[1558]113 }
[8011]114 else if ( miscIntr.indexOf("15 min") != -1 ) {
115 _miscIntr = 900;
[1558]116 }
[8011]117 else if ( miscIntr.indexOf("1 hour") != -1 ) {
118 _miscIntr = 3600;
[1558]119 }
[8011]120 else if ( miscIntr.indexOf("6 hours") != -1 ) {
121 _miscIntr = 21600;
[1558]122 }
[8011]123 else if ( miscIntr.indexOf("1 day") != -1 ) {
124 _miscIntr = 86400;
[1558]125 }
126
127 // RTCM message types
128 // ------------------
129 _checkMountPoint = settings.value("miscMount").toString();
130
131 // Initialize private members
132 // --------------------------
133 _wrongEpoch = false;
[1568]134 _checkSeg = false;
[1558]135 _numSucc = 0;
136 _secSucc = 0;
137 _secFail = 0;
[1568]138 _initPause = 0;
[1558]139 _currPause = 0;
[8082]140 _endCorrupt = false;
141 _begCorrupt = false;
142 _fromCorrupt = false;
[1558]143
[1568]144 _checkTime = QDateTime::currentDateTime();
[6805]145 _decodeSucc = QDateTime::currentDateTime();
[6769]146
[6805]147 _decodeStop = QDateTime::currentDateTime();
148 _begDateTimeOut = QDateTime::currentDateTime();
149 _endDateTimeOut = QDateTime::currentDateTime();
[6820]150 _fromReconnect = false;
[1568]151
[6820]152 _decodeStopCorr = QDateTime::currentDateTime();
153 _begDateTimeCorr = QDateTime::currentDateTime();
154 _endDateTimeCorr = QDateTime::currentDateTime();
[1558]155}
156
157// Destructor
158//////////////////////////////////////////////////////////////////////////////
159latencyChecker::~latencyChecker() {
160}
161
[1568]162// Perform 'Begin outage' check
[1558]163//////////////////////////////////////////////////////////////////////////////
[1568]164void latencyChecker::checkReconnect() {
[1572]165
[6768]166 if (_inspSegm == 0) { return;}
[6758]167
[1572]168 // Begin outage threshold
169 // ----------------------
[6805]170 if (!_fromReconnect) {
171 _endDateTimeOut = QDateTime::currentDateTime();
[8011]172 }
[6805]173 _fromReconnect = true;
174
[1572]175 if ( _decodeStop.isValid() ) {
[6805]176 _begDateTimeOut = QDateTime::currentDateTime();
177 if ( _endDateTimeOut.secsTo(QDateTime::currentDateTime()) > _adviseFail * 60 ) {
178 _begDateOut = _endDateTimeOut.toUTC().date().toString("yy-MM-dd");
179 _begTimeOut = _endDateTimeOut.toUTC().time().toString("hh:mm:ss");
180 emit(newMessage((_staID + ": Failure threshold exceeded, outage since "
[8204]181 + _begDateOut + " " + _begTimeOut + " UTC").toLatin1(), true));
[6807]182 callScript(("Begin_Outage "
[8204]183 + _begDateOut + " " + _begTimeOut + " UTC").toLatin1());
[1572]184 _decodeStop.setDate(QDate());
185 _decodeStop.setTime(QTime());
[6805]186 _decodeStart = QDateTime::currentDateTime();
[1572]187 }
188 }
[1568]189}
190
191// Perform Corrupt and 'End outage' check
192//////////////////////////////////////////////////////////////////////////////
[1562]193void latencyChecker::checkOutage(bool decoded) {
[1558]194
[1568]195 if (_inspSegm == 0) { return;}
[1558]196
[1568]197 if (decoded) { _numSucc += 1; }
[1558]198
[1568]199 if (!_checkPause.isValid() || _checkPause.secsTo(QDateTime::currentDateTime()) >= _currPause ) {
200 if (!_checkSeg) {
201 if ( _checkTime.secsTo(QDateTime::currentDateTime()) > _inspSegm ) {
202 _checkSeg = true;
[1558]203 }
204 }
[1568]205
206 // Check - once per inspect segment
207 // --------------------------------
208 if (_checkSeg) {
209
210 _checkTime = QDateTime::currentDateTime();
211
212 if (_numSucc > 0) {
213 _secSucc += _inspSegm;
[6820]214 _secFail = 0;
[1568]215 _decodeSucc = QDateTime::currentDateTime();
216 if (_secSucc > _adviseReco * 60) {
217 _secSucc = _adviseReco * 60 + 1;
218 }
219 _numSucc = 0;
220 _currPause = _initPause;
221 _checkPause.setDate(QDate());
222 _checkPause.setTime(QTime());
[1558]223 }
224 else {
[1568]225 _secFail += _inspSegm;
226 _secSucc = 0;
[6805]227 if (_secFail > _adviseFail * 60) {
[1568]228 _secFail = _adviseFail * 60 + 1;
[1558]229 }
[1568]230 if (!_checkPause.isValid()) {
231 _checkPause = QDateTime::currentDateTime();
232 }
233 else {
234 _checkPause.setDate(QDate());
235 _checkPause.setTime(QTime());
236 _secFail = _secFail + _currPause - _inspSegm;
237 _currPause = _currPause * 2;
238 if (_currPause > 960) {
239 _currPause = 960;
240 }
241 }
[1558]242 }
[6805]243
[6820]244 // Begin corrupt threshold
245 // -----------------------
246 if (_secSucc > 0) {
247 _endDateTimeCorr = QDateTime::currentDateTime();
[6805]248 }
[1568]249
[6820]250 if (_secFail > 0) {
251 _begDateTimeCorr = QDateTime::currentDateTime();
252 }
253
254 if ( _decodeStopCorr.isValid() ) {
255 _begDateTimeCorr = QDateTime::currentDateTime();
256 if ( _endDateTimeCorr.secsTo(QDateTime::currentDateTime()) > _adviseFail * 60 ) {
257 _begDateCorr = _endDateTimeCorr.toUTC().date().toString("yy-MM-dd");
258 _begTimeCorr = _endDateTimeCorr.toUTC().time().toString("hh:mm:ss");
259 emit(newMessage((_staID + ": Failure threshold exceeded, corrupted since "
[8204]260 + _begDateCorr + " " + _begTimeCorr + " UTC").toLatin1(), true));
[6805]261 callScript(("Begin_Corrupted "
[8204]262 + _begDateCorr + " " + _begTimeCorr + " UTC").toLatin1());
[1568]263 _secSucc = 0;
264 _numSucc = 0;
[6820]265 _decodeStopCorr.setDate(QDate());
266 _decodeStopCorr.setTime(QTime());
267 _decodeStartCorr = QDateTime::currentDateTime();
[1568]268 }
[8011]269 }
[6820]270 else {
271
272 // End corrupt threshold
273 // ---------------------
274 if ( _decodeStartCorr.isValid() ) {
275 _endDateTimeCorr = QDateTime::currentDateTime();
276 if ( _begDateTimeCorr.secsTo(QDateTime::currentDateTime()) > _adviseReco * 60 ) {
277 _endDateCorr = _begDateTimeCorr.toUTC().date().toString("yy-MM-dd");
278 _endTimeCorr = _begDateTimeCorr.toUTC().time().toString("hh:mm:ss");
279 emit(newMessage((_staID + ": Recovery threshold exceeded, corruption ended "
[8204]280 + _endDateCorr + " " + _endTimeCorr + " UTC").toLatin1(), true));
[6820]281 callScript(("End_Corrupted "
282 + _endDateCorr + " " + _endTimeCorr + " UTC Begin was "
[8204]283 + _begDateCorr + " " + _begTimeCorr + " UTC").toLatin1());
[6820]284 _decodeStartCorr.setDate(QDate());
285 _decodeStartCorr.setTime(QTime());
286 _decodeStopCorr = QDateTime::currentDateTime();
287 _secFail = 0;
288 }
289 }
[1558]290 }
[1568]291 _checkSeg = false;
[1558]292 }
293 }
[1568]294
[6805]295 // End outage threshold
296 // --------------------
[6758]297 if (_fromReconnect) {
[6805]298 _begDateTimeOut = QDateTime::currentDateTime();
[8011]299 }
[6805]300 _fromReconnect = false;
[6758]301
[1568]302 if ( _decodeStart.isValid() ) {
[6805]303 _endDateTimeOut = QDateTime::currentDateTime();
304 if ( _begDateTimeOut.secsTo(QDateTime::currentDateTime()) > _adviseReco * 60 ) {
305 _endDateOut = _begDateTimeOut.toUTC().date().toString("yy-MM-dd");
306 _endTimeOut = _begDateTimeOut.toUTC().time().toString("hh:mm:ss");
307 emit(newMessage((_staID + ": Recovery threshold exceeded, outage ended "
[8204]308 + _endDateOut + " " + _endTimeOut + " UTC").toLatin1(), true));
[6805]309 callScript(("End_Outage "
310 + _endDateOut + " " + _endTimeOut + " UTC Begin was "
[8204]311 + _begDateOut + " " + _begTimeOut + " UTC").toLatin1());
[1568]312 _decodeStart.setDate(QDate());
313 _decodeStart.setTime(QTime());
[6805]314 _decodeStop = QDateTime::currentDateTime();
[1558]315 }
316 }
[1568]317}
318
[1562]319// Perform latency checks (observations)
320//////////////////////////////////////////////////////////////////////////////
[6137]321void latencyChecker::checkObsLatency(const QList<t_satObs>& obsList) {
[1562]322
[7422]323 if (_miscIntr > 0 ) {
[8271]324 t_latency& l = _lObs;
[9277]325 l._type = "Observations";
[6137]326 QListIterator<t_satObs> it(obsList);
[1978]327 while (it.hasNext()) {
[6137]328 const t_satObs& obs = it.next();
[8011]329 bool wrongObservationEpoch = checkForWrongObsEpoch(obs._time);
[8372]330 l._newSec = static_cast<int>(nint(obs._time.gpssec()*10));
[8082]331 if (l._newSec > l._oldSec && !wrongObservationEpoch) {
[8374]332 if (l._newSec % (_miscIntr * 10) < l._oldSec % (_miscIntr * 10)) {
[8082]333 if (l._numLat > 0) {
334 if (l._meanDiff > 0.0) {
[1978]335 if ( _checkMountPoint == _staID || _checkMountPoint == "ALL" ) {
[8082]336 emit( newMessage(QString("%1 %2: Mean latency %3 sec, min %4, max %5, rms %6, %7 epochs, %8 gaps")
[1558]337 .arg(_staID.data())
[8082]338 .arg(l._type.data())
[9277]339 .arg(int(l._sumLat/l._numLat*100)/100., 4, 'f', 2)
340 .arg(int(l._minLat*100)/100., 4, 'f', 2)
341 .arg(int(l._maxLat*100)/100., 4, 'f', 2)
342 .arg(int((sqrt(l._sumLatQ / l._numLat))*100)/100., 4, 'f', 2)
[8082]343 .arg(l._numLat)
344 .arg(l._numGaps)
[8204]345 .toLatin1(), true) );
[1978]346 }
347 } else {
348 if ( _checkMountPoint == _staID || _checkMountPoint == "ALL" ) {
[8082]349 emit( newMessage(QString("%1 %2: Mean latency %3 sec, min %4, max %5, rms %6, %7 epochs")
[1558]350 .arg(_staID.data())
[8082]351 .arg(l._type.data())
[9277]352 .arg(int(l._sumLat/l._numLat*100)/100., 4, 'f', 2)
353 .arg(int(l._minLat*100)/100., 4, 'f', 2)
354 .arg(int(l._maxLat*100)/100., 4, 'f', 2)
355 .arg(int((sqrt(l._sumLatQ / l._numLat))*100)/100., 4, 'f', 2)
[8082]356 .arg(l._numLat)
[8204]357 .toLatin1(), true) );
[1558]358 }
359 }
[8673]360 l._meanDiff = l._diffSec / l._numLat;
361 l.init();
[1558]362 }
[1978]363 }
[8082]364 if (l._followSec) {
365 l._diffSec += l._newSec - l._oldSec;
[8673]366 if (l._meanDiff > 0.0) {
[8082]367 if (l._newSec - l._oldSec > 1.5 * l._meanDiff) {
368 l._numGaps += 1;
[1558]369 }
370 }
[1978]371 }
[1558]372
[1978]373 // Compute the observations latency
374 // --------------------------------
[6137]375 int week;
376 double sec;
[1978]377 currentGPSWeeks(week, sec);
378 const double secPerWeek = 7.0 * 24.0 * 3600.0;
[6137]379 if (week < int(obs._time.gpsw())) {
[1978]380 week += 1;
381 sec -= secPerWeek;
[1558]382 }
[6137]383 if (week > int(obs._time.gpsw())) {
[1978]384 week -= 1;
385 sec += secPerWeek;
386 }
[8082]387 l._curLat = sec - obs._time.gpssec();
388 l._sumLat += l._curLat;
389 l._sumLatQ += l._curLat * l._curLat;
390 if (l._curLat < l._minLat) {
391 l._minLat = l._curLat;
[1978]392 }
[8082]393 if (l._curLat >= l._maxLat) {
394 l._maxLat = l._curLat;
[1978]395 }
[8082]396 l._numLat += 1;
397 l._followSec = true;
[1558]398 }
[8271]399 l._oldSec = l._newSec;
[1558]400 }
[8082]401 _lObs = l;
402 setCurrentLatency(l._curLat);
[1558]403 }
404}
405
[1562]406// Perform latency checks (corrections)
407//////////////////////////////////////////////////////////////////////////////
[8082]408void latencyChecker::checkCorrLatency(int corrGPSEpochTime, int type) {
[1566]409 if (corrGPSEpochTime < 0) {
[8271]410 return;
[1562]411 }
[8082]412 t_latency& l = _lOrb; // init
413 switch (type) {
414 case 1057: case 1063: case 1240: case 1246: case 1252: case 1258:
[9124]415 l = _lOrb;
[9277]416 l._type = "RtcmSsrOrbit";
[8082]417 break;
418 case 1058: case 1064: case 1241: case 1247: case 1253: case 1259:
[9124]419 l = _lClk;
[9277]420 l._type = "RtcmSsrClock";
[8082]421 break;
422 case 1060: case 1066: case 1243: case 1249: case 1255: case 1261:
[9124]423 l = _lClkOrb;
424 l._type = "RtcmSsrClock&Orbit";
[8082]425 break;
426 case 1059: case 1065: case 1242: case 1248: case 1254: case 1260:
[9124]427 l = _lCb;
[9277]428 l._type = "RtcmSsrCodeBiases";
[8082]429 break;
[9124]430 case 1265: case 1266: case 1267: case 1268: case 1269: case 1270:
431 l = _lPb;
432 l._type = "RtcmSsrPhaseBiases";
[8082]433 break;
434 case 1264:
[9124]435 l = _lVtec;
[9277]436 l._type = "RtcmSsrVTEC";
[8082]437 break;
438 case 1061: case 1067: case 1244: case 1250: case 1256: case 1262:
[9124]439 l = _lUra;
[9277]440 l._type = "RtcmSsrURA";
[8082]441 break;
442 case 1062: case 1068: case 1245: case 1251: case 1257: case 1263:
[9124]443 l = _lHr;
[9277]444 l._type = "RtcmSsrHrClock";
[8082]445 break;
[9124]446 case 4076:
447 l = _lSsrIgs;
[9277]448 l._type = "RtcmSsrIgs4076";
[9124]449 break;
[8082]450 default:
451 return;
452 }
[1562]453
[7422]454 if (_miscIntr > 0) {
[8082]455 l._newSec = corrGPSEpochTime;
456 if (l._newSec > l._oldSec) {
457 if (int(l._newSec) % _miscIntr < int(l._oldSec) % _miscIntr) {
458 if (l._numLat>0) {
[1978]459 QString late;
[8082]460 if (l._meanDiff>0.) {
461 late = QString(" %1: Mean latency %2 sec, min %3, max %4, rms %5, %6 epochs, %7 gaps")
462 .arg(l._type.data())
[9277]463 .arg(int(l._sumLat/l._numLat*100)/100., 4, 'f', 2)
464 .arg(int(l._minLat*100)/100., 4, 'f', 2)
465 .arg(int(l._maxLat*100)/100., 4, 'f', 2)
466 .arg(int((sqrt(l._sumLatQ / l._numLat))*100)/100., 4, 'f', 2)
[8082]467 .arg(l._numLat)
468 .arg(l._numGaps);
[1978]469 if ( _checkMountPoint == _staID || _checkMountPoint == "ALL" ) {
[8204]470 emit(newMessage(QString(_staID + late ).toLatin1(), true) );
[1562]471 }
[8011]472 }
[1978]473 else {
[8082]474 late = QString(" %1: Mean latency %2 sec, min %3, max %4, rms %5, %6 epochs")
475 .arg(l._type.data())
[9277]476 .arg(int(l._sumLat/l._numLat*100)/100., 4, 'f', 2)
477 .arg(int(l._minLat*100)/100., 4, 'f', 2)
478 .arg(int(l._maxLat*100)/100., 4, 'f', 2)
479 .arg(int((sqrt(l._sumLatQ / l._numLat))*100)/100., 4, 'f', 2)
[8082]480 .arg(l._numLat);
[1978]481 if ( _checkMountPoint == _staID || _checkMountPoint == "ALL" ) {
[8204]482 emit(newMessage(QString(_staID + late ).toLatin1(), true) );
[1978]483 }
[1564]484 }
[1566]485 }
[8082]486 l._meanDiff = int(l._diffSec)/l._numLat;
487 l.init();
[1978]488 }
[8082]489 if (l._followSec) {
490 l._diffSec += l._newSec - l._oldSec;
491 if (l._meanDiff>0.) {
492 if (l._newSec - l._oldSec > 1.5 * l._meanDiff) {
493 l._numGaps += 1;
[1562]494 }
495 }
496 }
[8271]497
498 // Compute the observations latency
499 // --------------------------------
500 int week;
501 double sec;
502 currentGPSWeeks(week, sec);
503 double dt = fabs(sec - l._newSec);
504 const double secPerWeek = 7.0 * 24.0 * 3600.0;
505 if (dt > 0.5 * secPerWeek) {
506 if (sec > l._newSec) {
507 sec -= secPerWeek;
508 } else {
509 sec += secPerWeek;
510 }
511 }
[8082]512 l._curLat = sec - l._newSec;
513 l._sumLat += l._curLat;
514 l._sumLatQ += l._curLat * l._curLat;
515 if (l._curLat < l._minLat) {
516 l._minLat = l._curLat;
[1978]517 }
[8082]518 if (l._curLat >= l._maxLat) {
519 l._maxLat = l._curLat;
[1978]520 }
[8082]521 l._numLat += 1;
522 l._followSec = true;
523 setCurrentLatency(l._curLat);
[1562]524 }
[8271]525 l._oldSec = l._newSec;
[1562]526 }
[8271]527 switch (type) {
528 case 1057: case 1063: case 1240: case 1246: case 1252: case 1258:
529 _lOrb = l;
530 break;
531 case 1058: case 1064: case 1241: case 1247: case 1253: case 1259:
532 _lClk = l;
533 break;
534 case 1060: case 1066: case 1243: case 1249: case 1255: case 1261:
535 _lClkOrb = l;
536 break;
537 case 1059: case 1065: case 1242: case 1248: case 1254: case 1260:
538 _lCb = l;
539 break;
540 case 1265: case 1266: case 1267: case 1268: case 1269: case 1270:
541 _lPb = l;
542 break;
543 case 1264:
544 _lVtec = l;
545 break;
546 case 1061: case 1067: case 1244: case 1250: case 1256: case 1262:
547 _lUra = l;
548 break;
549 case 1062: case 1068: case 1245: case 1251: case 1257: case 1263:
550 _lHr = l;
551 break;
[9124]552 case 4076:
553 _lSsrIgs = l;
554 break;
[8271]555 }
[1562]556}
557
[8011]558// Call advisory notice script
[1558]559////////////////////////////////////////////////////////////////////////////
560void latencyChecker::callScript(const char* comment) {
561 if (!_adviseScript.isEmpty()) {
562#ifdef WIN32
[1577]563 Sleep(1);
[1558]564 QProcess::startDetached(_adviseScript, QStringList() << _staID << comment) ;
565#else
[1577]566 sleep(1);
[1558]567 QProcess::startDetached("nohup", QStringList() << _adviseScript << _staID << comment) ;
568#endif
569 }
570}
Note: See TracBrowser for help on using the repository browser.