[9597] | 1 | package Bnc;
|
---|
| 2 |
|
---|
| 3 | # Perl utility functions for BNC
|
---|
| 4 | #
|
---|
| 5 | # Revision: $Header: trunk/BNC/scripts/Bnc.pm 10585 2024-12-10 14:55:40Z stuerze $
|
---|
| 6 |
|
---|
| 7 | use strict;
|
---|
| 8 | use warnings;
|
---|
| 9 | use File::Basename;
|
---|
| 10 | use File::Spec::Functions qw(catfile);
|
---|
| 11 | use File::Temp qw(tempfile);
|
---|
| 12 | use Exporter;
|
---|
| 13 | use Time::Piece 1.30;
|
---|
| 14 | use PDL::Lite; # to avoid namespace pollution
|
---|
| 15 | use PDL::Primitive;
|
---|
| 16 | use Log::Log4perl qw(:easy);
|
---|
| 17 |
|
---|
| 18 | # use List::MoreUtils qw(uniq); # Prototype mismatch with PDL (uniq)
|
---|
| 19 |
|
---|
| 20 | # =============================================================================
|
---|
| 21 | # callBnc ($bnc_ini, %$opts_ref)
|
---|
| 22 | # =============================================================================
|
---|
| 23 | # Call BNC
|
---|
| 24 | #
|
---|
| 25 | # Param : $bnc_ini [optional] The BNC config file that should be used. If not set, the default config is ised.
|
---|
| 26 | # $opts_ref [required] Hash with BNC config options as key. They will overwrite the settings from the config file.
|
---|
| 27 | # Return : BNC exit status
|
---|
| 28 | # =============================================================================
|
---|
| 29 | sub callBnc {
|
---|
| 30 | my ( $bnc, $bnc_ini, $opts ) = @_;
|
---|
| 31 |
|
---|
| 32 | my $config_file = "";
|
---|
| 33 | if ( $bnc_ini && -s $bnc_ini ) {
|
---|
| 34 | $config_file = "--conf $bnc_ini";
|
---|
| 35 | }
|
---|
| 36 | else {
|
---|
| 37 | DEBUG("callBnc: Use default bnc-ini file");
|
---|
| 38 | }
|
---|
| 39 |
|
---|
| 40 | my $opts_str = "";
|
---|
| 41 | if ($opts) { $opts_str = options2string($opts) }
|
---|
| 42 |
|
---|
| 43 | # my @cmd = (
|
---|
| 44 | # 'xvfb-run',
|
---|
| 45 | # "--server-args='-screen 0, 1280x1024x8'", # 1024x768x24
|
---|
| 46 | # "$bnc",
|
---|
| 47 | # "--nw",
|
---|
| 48 | # $opts_str,
|
---|
| 49 | # );
|
---|
| 50 |
|
---|
| 51 | #my $rc = call_system("xvfb-run -a -e /home/user/xvfb.err --server-args='-screen 0 1280x1024x8' $bnc --nw $config_file $opts_str");
|
---|
| 52 | return Common::runCmd("$bnc --nw $config_file $opts_str");
|
---|
| 53 | }
|
---|
| 54 |
|
---|
| 55 | # Converts options map to string used for calling BNC.
|
---|
| 56 | sub options2string {
|
---|
| 57 | my ($opts) = shift;
|
---|
| 58 |
|
---|
| 59 | my $opts_str = "";
|
---|
| 60 | if ($opts) {
|
---|
| 61 | foreach my $key ( keys %{$opts} ) {
|
---|
| 62 | $opts_str .= "--key $key" . ' "' . $opts->{$key} . '" ';
|
---|
| 63 | }
|
---|
| 64 | }
|
---|
| 65 | return $opts_str;
|
---|
| 66 | }
|
---|
| 67 |
|
---|
| 68 | # =============================================================================
|
---|
| 69 | # parseMessageTypesFromFile
|
---|
| 70 | # =============================================================================
|
---|
| 71 | # Parse Message-Types with repetition rate from a BNC/scanRTCM logfile
|
---|
| 72 | #
|
---|
[10090] | 73 | # Param : $logfile [required] Path of the BNC/scanRTCM logfile
|
---|
[9597] | 74 | # $caAbbr [optional] caster name or abbreviation
|
---|
[10090] | 75 | # $tmpPath [optional] write working files to this path
|
---|
[9597] | 76 | #
|
---|
| 77 | # Return : Message-types for each mountpoint (as Hash-Ref)
|
---|
| 78 | # Hash with mp as key and the list of messTypes as value
|
---|
| 79 | # Example: $VAR1 = { 'RIO10' => ['1004(1)','1006(15)','1008(15)',...],
|
---|
| 80 | # 'CLIB0' => ['1004(1)','1006(10)','1008(10)',...],
|
---|
| 81 | # };
|
---|
| 82 | # =============================================================================
|
---|
| 83 | sub parseMessageTypesFromFile {
|
---|
| 84 | my ( $logfile, $caAbbr, $mytmpPath ) = @_;
|
---|
| 85 |
|
---|
| 86 | unless ( -s $logfile ) {
|
---|
| 87 | ERROR "File [$logfile] is empty or does not exist";
|
---|
| 88 | return;
|
---|
| 89 | }
|
---|
| 90 |
|
---|
| 91 | my $tmp = File::Temp->new( UNLINK => 1, SUFFIX => '.messtyps' );
|
---|
| 92 | my $casterMessTypeFile = $tmp->filename;
|
---|
| 93 |
|
---|
| 94 | #my $caster = $logfilename =~ s/\.log\.messtyps//r;
|
---|
| 95 | #INFO "Process caster '$caster'";
|
---|
| 96 |
|
---|
| 97 | INFO "Process $logfile";
|
---|
| 98 |
|
---|
| 99 | # scan beginns with that line: 16-02-18 23:58:02 WTZ37: Get data in RTCM 3.x format
|
---|
| 100 |
|
---|
| 101 | # First grep for message type lines in bnc-logfile and write them to local temp. directory
|
---|
| 102 | # NOTE: 'sort -u' because Ephemeries message-types 1019 (GPS), 1020 (GLONASS), 1045 (Galileo) come
|
---|
| 103 | # for every sat. at the same time/second. This is done for getting the repetition rate
|
---|
| 104 | system ("grep \"Received message type\" $logfile | sort -u > $casterMessTypeFile") == 0
|
---|
| 105 | or ERROR "Fehler: $!";
|
---|
| 106 |
|
---|
| 107 | # --------------------------------------------
|
---|
| 108 | # Get message types foreach station/mountpoint
|
---|
| 109 | # --------------------------------------------
|
---|
| 110 | my $cmd = "cat $casterMessTypeFile | awk '{print \$3, \$7}' | sort -u";
|
---|
| 111 | my @messTypes = `$cmd`; # ['DARX1: 1004\n','DARX1: 1006\n',... ]
|
---|
| 112 | if ( scalar @messTypes < 1 ) {
|
---|
| 113 | ERROR "Could not retrieve message types from file $casterMessTypeFile";
|
---|
| 114 | return;
|
---|
| 115 | }
|
---|
| 116 |
|
---|
| 117 | # Note: Skip first 1500 lines because BNC is weird here, all lines with same timestamp, buffer problem?
|
---|
| 118 |
|
---|
| 119 | # ------------------------------------------------------
|
---|
| 120 | # Guess repetition rate for each mountpoint/message-type
|
---|
| 121 | # ------------------------------------------------------
|
---|
| 122 | # For that get the first $minSamples appearances of mountp with same message type
|
---|
| 123 | # and build the differences between them
|
---|
| 124 | my $minSamples = 4;
|
---|
| 125 | foreach my $mpType (@messTypes) {
|
---|
| 126 | chomp $mpType; # 'AUBG3: 1004'
|
---|
| 127 | my ( $mp, $mt ) = split ( /:\s*/, $mpType );
|
---|
| 128 | my $cmd = "grep \"$mp: Received message type $mt\" $logfile";
|
---|
| 129 | my @rows = `$cmd`;
|
---|
| 130 | if ( scalar @rows < 1 ) {
|
---|
| 131 | ERROR "Could not retrieve message types for $mpType from file $casterMessTypeFile";
|
---|
| 132 | next;
|
---|
| 133 | }
|
---|
| 134 |
|
---|
| 135 | if ( scalar @rows <= $minSamples ) {
|
---|
| 136 | WARN "Could not guess repetition rate for message type $mpType: only " . scalar @rows . " matches found";
|
---|
| 137 | next;
|
---|
| 138 | }
|
---|
| 139 |
|
---|
| 140 | my $repRate = _computeMessTypRepetitonRate( \@rows, $caAbbr );
|
---|
| 141 | if ($repRate) {
|
---|
| 142 | $mpType .= '(' . $repRate . ')';
|
---|
| 143 | }
|
---|
| 144 | } # ----- end foreach messageType -----
|
---|
| 145 |
|
---|
| 146 | #if ( unlink $casterMessTypeFile ) { TRACE "Removed file [$casterMessTypeFile]" }
|
---|
| 147 |
|
---|
| 148 | # Return an HASH OF ARRAYS with mp as key and the list of messTypes as value
|
---|
| 149 | my %messTypesHash;
|
---|
| 150 | foreach (@messTypes) {
|
---|
| 151 | my @ele = split ( ': ', $_ );
|
---|
| 152 | my $mp = shift @ele;
|
---|
| 153 | push ( @{ $messTypesHash{$mp} }, shift @ele );
|
---|
| 154 | }
|
---|
| 155 |
|
---|
| 156 | return \%messTypesHash;
|
---|
| 157 | }
|
---|
| 158 |
|
---|
| 159 | # =============================================================================
|
---|
| 160 | # _computeMessTypRepetitonRate
|
---|
| 161 | # =============================================================================
|
---|
| 162 | # Guess repetition rate of message types
|
---|
| 163 | #
|
---|
| 164 | # Param : $firstMatches [required] Array-Ref with first appearances of station
|
---|
| 165 | # and mess type. Complete logfile lines, e.g.
|
---|
| 166 | # '13-07-04 17:38:26 BRUX0: Received message type 1004 '
|
---|
| 167 | # $caAbbr [optional] caster abbreviation
|
---|
| 168 | # Return : repetition rate in secs, (median value)
|
---|
| 169 | # =============================================================================
|
---|
| 170 | sub _computeMessTypRepetitonRate {
|
---|
| 171 | my ( $rows, $caAbbr ) = @_;
|
---|
| 172 |
|
---|
| 173 | my $maxGap = 600; # if gap > 10min then we guess it is a new scan
|
---|
| 174 |
|
---|
| 175 | $rows->[0] =~ /.{18}([a-x0-9]+): Received message type (\d+)/i;
|
---|
| 176 | my $stat = $1;
|
---|
| 177 | my $mestp = $2;
|
---|
| 178 |
|
---|
| 179 | # Create list of unix timestamps
|
---|
| 180 | my @scans;
|
---|
| 181 | my $scan = 0;
|
---|
| 182 | my ( $prevTime, $deltaT ) = ( 0, 0 );
|
---|
| 183 | foreach (@$rows) {
|
---|
| 184 | my $uxtime = date2unix( substr ( $_, 0, 17 ) );
|
---|
| 185 | if ( !$uxtime ) {
|
---|
| 186 | ERROR "Could not parse date from line $_";
|
---|
| 187 | next;
|
---|
| 188 | }
|
---|
| 189 | $deltaT = $uxtime - $prevTime;
|
---|
| 190 | next if ( $deltaT == 0 ); # e.g. eph 1019,1020 one message for each sat.
|
---|
| 191 | next if ( $deltaT <= 1 && $mestp =~ /10(19|20|42|43|44|45|46)|63/ );
|
---|
| 192 | if ( $prevTime && $deltaT > $maxGap ) {
|
---|
| 193 | $scan++;
|
---|
| 194 | }
|
---|
| 195 | push ( @{ $scans[$scan] }, $uxtime );
|
---|
| 196 | $prevTime = $uxtime;
|
---|
| 197 | }
|
---|
| 198 |
|
---|
| 199 | my @repRates;
|
---|
| 200 | my $highest_nof_diffs = 0;
|
---|
| 201 | foreach (@scans) {
|
---|
| 202 | my @timestamps = @{$_};
|
---|
| 203 |
|
---|
| 204 | # Compute the differences
|
---|
| 205 | my @diffs;
|
---|
| 206 | for ( my $i = 1; $i <= $#timestamps; $i++ ) {
|
---|
| 207 | push ( @diffs, $timestamps[$i] - $timestamps[ $i - 1 ] );
|
---|
| 208 | }
|
---|
| 209 | my $nof_diffs = scalar @diffs;
|
---|
| 210 |
|
---|
| 211 | if ( $nof_diffs < 2 ) {
|
---|
| 212 | WARN("$stat: $mestp: only $nof_diffs diffs");
|
---|
| 213 | next;
|
---|
| 214 | }
|
---|
| 215 |
|
---|
| 216 | my ( $mean, $prms, $median, $min, $max, $adev, $rms_n ) = stats( pdl \@diffs );
|
---|
| 217 | $mean = sprintf ( "%.0f", $mean );
|
---|
| 218 | $rms_n = sprintf ( "%.02f", $rms_n );
|
---|
| 219 | print $stat, ": ", $mestp, ": ", join ( ' ', @diffs ), "[Sig: $mean, $rms_n]\n";
|
---|
| 220 |
|
---|
| 221 | if ( $rms_n > 10 ) {
|
---|
| 222 | WARN("$stat: $mestp: RMS too high: $rms_n");
|
---|
| 223 | next;
|
---|
| 224 | }
|
---|
| 225 |
|
---|
| 226 | # get the most frequent value
|
---|
| 227 | my %counti = ();
|
---|
| 228 | $counti{$_}++ foreach (@diffs);
|
---|
| 229 | my ( $ni, $mfv ) = ( 0, 0 );
|
---|
| 230 | while ( my ( $k, $v ) = each %counti ) {
|
---|
| 231 | if ( $v > $ni ) {
|
---|
| 232 | $ni = $v;
|
---|
| 233 | $mfv = $k;
|
---|
| 234 | }
|
---|
| 235 | }
|
---|
| 236 |
|
---|
| 237 | my $rounded_val = $mfv; # init
|
---|
| 238 | foreach ( ( 1, 5, 10, 15, 30, 60, 120, 150, 300 ) ) { # most likely values
|
---|
| 239 | my $mdiff = abs ( $mfv - $_ );
|
---|
| 240 | if ( $mdiff <= 2 ) {
|
---|
| 241 | $rounded_val = $_;
|
---|
| 242 | }
|
---|
| 243 | }
|
---|
| 244 | push ( @repRates, [ $rounded_val, $nof_diffs ] );
|
---|
| 245 |
|
---|
| 246 | if ( $nof_diffs > $highest_nof_diffs ) {
|
---|
| 247 | $highest_nof_diffs = $nof_diffs;
|
---|
| 248 | }
|
---|
| 249 | } # ----- end foreach scan -----
|
---|
| 250 |
|
---|
| 251 | my @mostLikelyRates = grep { $_->[1] == $highest_nof_diffs } @repRates;
|
---|
| 252 | my $mostLikelyRate = $mostLikelyRates[0]->[0];
|
---|
| 253 | foreach (@repRates) {
|
---|
| 254 | if ( abs ( $_->[0] - $mostLikelyRate ) > 2 ) {
|
---|
| 255 | ERROR "$stat: $caAbbr: $mestp: repetition rates from different scans differ: $mostLikelyRate $_->[0]";
|
---|
| 256 | if ( scalar @repRates == 2 ) {
|
---|
| 257 | return;
|
---|
| 258 | }
|
---|
| 259 | }
|
---|
| 260 | }
|
---|
| 261 |
|
---|
| 262 | return $mostLikelyRate;
|
---|
| 263 | }
|
---|
| 264 |
|
---|
| 265 | # =============================================================================
|
---|
| 266 | # parseConfig ($confFile)
|
---|
| 267 | # =============================================================================
|
---|
| 268 | # Parse the BNC config file.
|
---|
| 269 | #
|
---|
| 270 | # Param : $confFile [required] BNC config file
|
---|
| 271 | # Return : Hash with configuration on success, otherwise undef
|
---|
| 272 | # Usage : $bncConf = parseConf($bncConfFile);
|
---|
| 273 | # $corrMount = $bncConf->{'PPP'}->{'corrMount'};
|
---|
| 274 | # =============================================================================
|
---|
| 275 | sub parseConfig {
|
---|
| 276 | my ($confFile) = @_;
|
---|
| 277 |
|
---|
| 278 | -s $confFile || LOGDIE "BNC config file \"$confFile\" does not exist\n";
|
---|
| 279 | TRACE "Parse BNC config file $confFile";
|
---|
| 280 | open ( my $INP, '<', $confFile ) || die "Could not open file '$confFile': $!";
|
---|
| 281 | my @confLines = <$INP>;
|
---|
| 282 | close ($INP);
|
---|
| 283 |
|
---|
| 284 | my %conf;
|
---|
| 285 | my $section; # [General], [PPP]
|
---|
| 286 | foreach (@confLines) {
|
---|
| 287 | chomp;
|
---|
| 288 | s/#.*//; # entfernt Kommentare
|
---|
| 289 | s/^\s*//; # whitespace am Anfang entfernen
|
---|
| 290 | s/\s+$//; # entfernt alle whitespaces am Ende
|
---|
| 291 | next unless length;
|
---|
| 292 | if ( $_ =~ /\[(\S+)\]/ ) { $section = $1 }
|
---|
| 293 | next if ( !$section );
|
---|
| 294 | my ( $key, $val ) = split ( /\s*=\s*/, $_, 2 );
|
---|
| 295 | if ( !defined $val ) { $val = "" }
|
---|
| 296 |
|
---|
| 297 | if ( $key eq "mountPoints" ) {
|
---|
| 298 |
|
---|
| 299 | # Simple parsing
|
---|
| 300 | $val =~ s/^\/\///;
|
---|
| 301 | my @mpts = split ( /,\s?\/{2}/, $val );
|
---|
| 302 | $conf{$section}->{$key} = \@mpts;
|
---|
| 303 |
|
---|
| 304 | # Extended parsing
|
---|
| 305 | my @mpts_def = ();
|
---|
| 306 | foreach (@mpts) {
|
---|
[10090] | 307 |
|
---|
[9597] | 308 | # user:passwd@igs-ip.net:2101/ASPA0 RTCM_3.0 ASM -14.33 189.28 no 1
|
---|
| 309 | if ( $_ =~
|
---|
| 310 | /^([\w-]+):(.+[^@])@([\w\.-]+):(\d{3,5})\/([-\w]+) ([\w\.]+) ?(\w{3})? ([\+\-\d\.]+) ([\+\-\d\.]+) no (\w+)/i
|
---|
| 311 | )
|
---|
| 312 | {
|
---|
| 313 | push ( @mpts_def, { caster => $3, port => $4, mp => $5, ntripVers => $10 } );
|
---|
| 314 | }
|
---|
| 315 | else { ERROR "$confFile: Could not parse mountPoints string $_" }
|
---|
| 316 | }
|
---|
| 317 | $conf{$section}->{'mountPoints_parsed'} = \@mpts_def;
|
---|
| 318 | }
|
---|
| 319 | elsif ( $key eq "cmbStreams" ) {
|
---|
| 320 | my @cmbStrs = split ( /\s*,\s*/, $val );
|
---|
| 321 | foreach (@cmbStrs) {
|
---|
| 322 | s/"//g;
|
---|
| 323 | s/\s+$//; # entfernt alle whitespaces am Ende
|
---|
| 324 | }
|
---|
| 325 | $conf{$section}->{$key} = \@cmbStrs;
|
---|
| 326 | }
|
---|
| 327 | else { $conf{$section}->{$key} = $val }
|
---|
| 328 | }
|
---|
| 329 |
|
---|
| 330 | my @nofPar = keys %conf;
|
---|
| 331 | if ( scalar @nofPar < 1 ) {
|
---|
| 332 | ERROR "No parameter found in BNC conf \"$confFile\"";
|
---|
| 333 | return;
|
---|
| 334 | }
|
---|
| 335 | return \%conf;
|
---|
| 336 | }
|
---|
| 337 |
|
---|
| 338 | # =============================================================================
|
---|
| 339 | # parseLogfile ($file, $sampling, $goBackSecs, $logMode )
|
---|
| 340 | # =============================================================================
|
---|
| 341 | # Parse BNCs' logfile
|
---|
| 342 | #
|
---|
| 343 | # Param : $file [required] BNC logfile
|
---|
| 344 | # $sampling [optional] sampling rate for logfile
|
---|
| 345 | # $logMode [optional] Flag. If set, remember the position of the file-read
|
---|
| 346 | # for the next read. Default: off
|
---|
| 347 | # Return : \%data
|
---|
| 348 | # =============================================================================
|
---|
| 349 | sub parseLogfile {
|
---|
| 350 | my $file = shift;
|
---|
| 351 | my $sampling = shift // 1;
|
---|
| 352 | my $logMode = shift // 0;
|
---|
| 353 |
|
---|
| 354 | open ( my $fh, "<", $file ) || LOGDIE "Could not open file $file: $!\n";
|
---|
| 355 |
|
---|
| 356 | # Goto last position from last read
|
---|
| 357 | #my $fPos = filePosition($file);
|
---|
| 358 | #TRACE "Current file pos: $fPos";
|
---|
[10514] | 359 | $logMode && seek ( $fh, &Common::filePosition($file), 0 );
|
---|
[9597] | 360 |
|
---|
| 361 | #$logMode && seek ( $fh, $fPos, 0 );
|
---|
| 362 | my $ln = "";
|
---|
| 363 | my ( @hlp, @epochs, @latencies, @restarts );
|
---|
| 364 | my $rec = {};
|
---|
| 365 | while (<$fh>) {
|
---|
| 366 | chomp ( $ln = $_ );
|
---|
| 367 | $rec = {};
|
---|
| 368 |
|
---|
| 369 | if ( $ln =~ /\bNEU/ ) { # NEU displacements
|
---|
| 370 | @hlp = split ( /\s+/, $ln );
|
---|
| 371 | my $tp = Time::Piece->strptime( substr ( $hlp[2], 0, 19 ), '%Y-%m-%d_%H:%M:%S' );
|
---|
| 372 |
|
---|
| 373 | if ( $hlp[14] eq '-nan' || $hlp[15] eq '-nan' || $hlp[16] eq '-nan' ) {
|
---|
| 374 | WARN("$hlp[2] $hlp[3]: NEU displacements are NAN");
|
---|
| 375 | next;
|
---|
| 376 | }
|
---|
| 377 |
|
---|
| 378 | #DEBUG ($tp->epoch, $hlp[3]);
|
---|
| 379 | push (
|
---|
| 380 | @epochs,
|
---|
| 381 | {
|
---|
| 382 | time => $tp->epoch,
|
---|
| 383 | site => $hlp[3],
|
---|
| 384 | dN => $hlp[14],
|
---|
| 385 | dE => $hlp[15],
|
---|
| 386 | dU => $hlp[16],
|
---|
| 387 | TRP => $hlp[18] + $hlp[19],
|
---|
| 388 | }
|
---|
| 389 | );
|
---|
| 390 | }
|
---|
| 391 | elsif ( index ( $ln, "latency", 25 ) > 1 ) {
|
---|
| 392 |
|
---|
| 393 | # DEBUG ($ln);
|
---|
| 394 | # altes format: 15-10-06 15:29:02 POTS0: Mean latency 2.34 sec, min 1.58, max 3, rms 0.48, 43 epochs, 17 gaps
|
---|
| 395 | # neu in BNC 2.12.4: 17-06-06 15:35:02 OHI37 Observations: Mean latency 1.51 sec, min 0.57, max 2.7, rms 0.5, 203 epochs, 73 gaps
|
---|
| 396 | @hlp = split ( /\s+/, $ln );
|
---|
| 397 |
|
---|
| 398 | # old latency log format
|
---|
| 399 | if ( $hlp[2] =~ /:$/ ) {
|
---|
| 400 | splice @hlp, 3, 0, 'Placeholder:';
|
---|
| 401 | $hlp[2] =~ s/:$//;
|
---|
| 402 | }
|
---|
| 403 | $hlp[3] =~ s/:$//;
|
---|
| 404 |
|
---|
| 405 | my $tp = Time::Piece->strptime( "$hlp[0] $hlp[1]", '%y-%m-%d %H:%M:%S' );
|
---|
| 406 | $rec = {
|
---|
| 407 | time => $tp->epoch,
|
---|
| 408 | mp => $hlp[2],
|
---|
| 409 | meanLat => $hlp[6] + 0.0,
|
---|
| 410 | epochs => int ( $hlp[14] ),
|
---|
| 411 | type => $hlp[3]
|
---|
| 412 | };
|
---|
| 413 |
|
---|
| 414 | # Unter bestimmten Bedingungen werden die gaps nicht rausgeschrieben!
|
---|
| 415 | if ( $ln =~ /gaps/ ) {
|
---|
| 416 | $rec->{'gaps'} = int ( $hlp[16] );
|
---|
| 417 | }
|
---|
| 418 |
|
---|
| 419 | push ( @latencies, $rec );
|
---|
| 420 | }
|
---|
| 421 | elsif ( index ( $ln, "Start BNC" ) > 1 ) {
|
---|
| 422 |
|
---|
| 423 | # 17-06-13 07:06:58 ========== Start BNC v2.12.3 (LINUX) ==========
|
---|
| 424 | @hlp = split ( /\s+/, $ln );
|
---|
| 425 | my $tp = Time::Piece->strptime( "$hlp[0] $hlp[1]", '%y-%m-%d %H:%M:%S' );
|
---|
| 426 | push (
|
---|
| 427 | @restarts,
|
---|
| 428 | {
|
---|
| 429 | time => $tp->epoch,
|
---|
| 430 | bncvers => $hlp[5]
|
---|
| 431 | }
|
---|
| 432 | );
|
---|
| 433 | }
|
---|
| 434 |
|
---|
| 435 | } # ----- next line -----
|
---|
| 436 |
|
---|
[10514] | 437 | $logMode && &Common::filePosition( $file, tell ($fh) ); # Remember pos for next read
|
---|
[9597] | 438 | close $fh;
|
---|
| 439 |
|
---|
| 440 | # Sampling must be done afterwords, for each station separated!
|
---|
| 441 | my @epochs_sampled;
|
---|
| 442 | my @sites = map { $_->{'site'} } @epochs;
|
---|
| 443 |
|
---|
| 444 | #@sites = uniq @sites;
|
---|
| 445 | my %hlp1 = ();
|
---|
| 446 | @sites = grep { !$hlp1{$_}++ } @sites;
|
---|
| 447 | foreach my $s (@sites) {
|
---|
| 448 | my $epoch_selected = 0;
|
---|
| 449 | foreach my $rec (@epochs) {
|
---|
| 450 | next if ( $rec->{'site'} ne $s );
|
---|
| 451 | if ( $rec->{'time'} - $epoch_selected >= $sampling ) {
|
---|
| 452 | push ( @epochs_sampled, $rec );
|
---|
| 453 | $epoch_selected = $rec->{'time'};
|
---|
| 454 | }
|
---|
| 455 | }
|
---|
| 456 | }
|
---|
| 457 |
|
---|
| 458 | my %data = (
|
---|
| 459 | EPOCHS => \@epochs_sampled,
|
---|
| 460 | LATENCIES => \@latencies,
|
---|
| 461 | RESTARTS => \@restarts
|
---|
| 462 | );
|
---|
| 463 |
|
---|
| 464 | return \%data;
|
---|
| 465 | }
|
---|
| 466 |
|
---|
| 467 | # =============================================================================
|
---|
| 468 | # parsePPPLogfile ($file, $sampling, $goBackSecs, $logMode )
|
---|
| 469 | # =============================================================================
|
---|
| 470 | # Parse BNCs' PPP station logfile
|
---|
| 471 | #
|
---|
| 472 | # Param : $file [required] BNC PPP station
|
---|
| 473 | # $sampling [optional] sampling rate for logfile
|
---|
| 474 | # $goBackSecs [optional] go back that seconds from now in logfile
|
---|
| 475 | # $logMode [optional] Flag. If set, remember the position of the file-read
|
---|
| 476 | # for the next read. Default: off
|
---|
| 477 | # Return : $station, \%data
|
---|
| 478 | # =============================================================================
|
---|
| 479 | sub parsePPPLogfile {
|
---|
| 480 | my $file = shift;
|
---|
| 481 | my $sampling = shift // 1;
|
---|
| 482 | my $goBackSecs = shift // 0;
|
---|
| 483 | my $logMode = shift // 0;
|
---|
| 484 |
|
---|
| 485 | if ($logMode) { $goBackSecs = 0 }
|
---|
| 486 |
|
---|
| 487 | my $startSec;
|
---|
| 488 | if ($goBackSecs) {
|
---|
| 489 | $startSec = time () - $goBackSecs;
|
---|
| 490 | }
|
---|
| 491 | my $epo;
|
---|
| 492 | my $old_epochSec = 0;
|
---|
| 493 | my $epochSec = 0;
|
---|
| 494 | my $epochDiff = 0;
|
---|
[10585] | 495 | my ( @hlp, @N, @E, @U, %SATNUM, @TRP);
|
---|
| 496 | my ( @EPOCH, );
|
---|
| 497 | my ( %RECCLK, %AMB, %RES, %ELE, %ION, %BIA);
|
---|
| 498 | my ( $station, $lki, $sys, $sat, $amb);
|
---|
[9597] | 499 | open ( my $fh, "<", $file ) || LOGDIE "Could not open file $file: $!\n";
|
---|
| 500 |
|
---|
| 501 | # Goto last position from last read
|
---|
| 502 | #my $fPos = filePosition($file);
|
---|
| 503 | #TRACE "Current file pos: $fPos";
|
---|
| 504 | $logMode && seek ( $fh, filePosition($file), 0 );
|
---|
| 505 |
|
---|
| 506 | #$logMode && seek ( $fh, $fPos, 0 );
|
---|
| 507 | my $ln = "";
|
---|
| 508 | while (<$fh>) {
|
---|
| 509 | chomp ( $ln = $_ );
|
---|
| 510 |
|
---|
| 511 | if ( $ln =~ /\bof Epoch\b/ ) {
|
---|
| 512 |
|
---|
| 513 | # PPP of Epoch 2015-08-27_14:00:15.000
|
---|
[9910] | 514 | if ( $ln =~ /PPP of Epoch (\d{4}-\d{2}-\d{2}_\d{2}:\d{2}:\d{2})\.\d+/ ) {
|
---|
[10090] | 515 | $epo = $1; #print "$epo\n";
|
---|
[9597] | 516 | }
|
---|
| 517 | else { ERROR "strange line: \"$ln\""; next }
|
---|
| 518 |
|
---|
| 519 | my $tp = Time::Piece->strptime( $epo, '%Y-%m-%d_%H:%M:%S' );
|
---|
| 520 | $epochSec = $tp->epoch();
|
---|
| 521 | $epochDiff = $epochSec - $old_epochSec;
|
---|
| 522 | next;
|
---|
| 523 | }
|
---|
| 524 |
|
---|
| 525 | next if ( !$epo );
|
---|
| 526 | next if ( defined $startSec && $epochSec < $startSec );
|
---|
| 527 | next if ( $epochDiff && $epochDiff < $sampling );
|
---|
| 528 |
|
---|
| 529 | @hlp = split ( /\s+/, $ln );
|
---|
| 530 |
|
---|
[9910] | 531 | if ( $ln =~ /\bdN\b/ ) {
|
---|
[10383] | 532 | push ( @EPOCH, $epochSec ); # besser $epo ?
|
---|
[9597] | 533 | $old_epochSec = $epochSec;
|
---|
| 534 |
|
---|
[10383] | 535 | #2015-08-27_13:59:50.000 DIEP1 X = 3842152.9054 +- 0.0242 Y = 563402.0331 +- 0.0176 Z = 5042888.5182 +- 0.0319 dN = 0.0130 +- 0.0193 dE = -0.0032 +- 0.0178 dU = -0.0248 +- 0.0349
|
---|
[9597] | 536 | $station = $hlp[1];
|
---|
| 537 |
|
---|
| 538 | if ( $hlp[19] eq '-nan' || $hlp[24] eq '-nan' || $hlp[29] eq '-nan' ) {
|
---|
| 539 | WARN("$hlp[0] $station: NEU displacements are NAN");
|
---|
| 540 | }
|
---|
| 541 |
|
---|
| 542 | push @N, $hlp[19];
|
---|
| 543 | push @E, $hlp[24];
|
---|
| 544 | push @U, $hlp[29];
|
---|
| 545 | }
|
---|
| 546 | elsif ( ( $ln =~ /\bAMB\b/ ) && ( $ln !~ /RESET/ ) ) {
|
---|
[10090] | 547 |
|
---|
| 548 | # 2015-08... AMB lIF G04 253.0000 -8.9924 +- 1.7825 el = 22.03 epo = 86
|
---|
| 549 | $lki = $hlp[2];
|
---|
| 550 | $sat = $hlp[3];
|
---|
| 551 | $sys = substr ( $sat, 0, 1 );
|
---|
| 552 | $amb = $hlp[4] + $hlp[5];
|
---|
| 553 | push @{ $AMB{$lki}{$sys}{$sat}{EPOCH} }, $epochSec;
|
---|
| 554 | push @{ $AMB{$lki}{$sys}{$sat}{DATA} }, $amb;
|
---|
| 555 | push @{ $AMB{$lki}{$sys}{$sat}{NUMEPO} }, $hlp[13];
|
---|
| 556 | push @{ $ELE{$sys}{$sat}{EPOCH} }, $epochSec;
|
---|
| 557 | push @{ $ELE{$sys}{$sat}{DATA} }, $hlp[10];
|
---|
[9597] | 558 | }
|
---|
| 559 | elsif ( $ln =~ /\bRES\b/ && $ln !~ /Neglected/ ) {
|
---|
[10090] | 560 |
|
---|
[9910] | 561 | # 2015-08... RES lIF G30 -0.0076
|
---|
| 562 | $sat = $hlp[3];
|
---|
| 563 | $lki = $hlp[2];
|
---|
[9597] | 564 | $sys = substr ( $sat, 0, 1 );
|
---|
| 565 |
|
---|
| 566 | #print "$epo $lki $sys $sat $res\n";
|
---|
| 567 | push @{ $RES{$lki}{$sys}{$sat}{EPOCH} }, $epochSec;
|
---|
| 568 | push @{ $RES{$lki}{$sys}{$sat}{DATA} }, $hlp[4];
|
---|
| 569 | }
|
---|
| 570 | elsif ( ( $ln =~ /\bION\b/ ) && ( $ln !~ /RESET/ ) ) {
|
---|
| 571 |
|
---|
| 572 | # 2018-12-01_20:37:58.000 ION G02 0.0000 -0.3277 +- 2.4663
|
---|
| 573 | $sat = $hlp[2];
|
---|
| 574 | $sys = substr ( $sat, 0, 1 );
|
---|
| 575 | push @{ $ION{$sys}{$sat}{EPOCH} }, $epochSec;
|
---|
| 576 | push @{ $ION{$sys}{$sat}{DATA} }, $hlp[4];
|
---|
| 577 | }
|
---|
| 578 | elsif ( ( $ln =~ /\bBIA\b/ ) && ( $ln !~ /RESET/ ) ) {
|
---|
| 579 |
|
---|
| 580 | # 2020-12-09_00:55:19.000 BIA c1 G 0.0000 +2.5149 +- 9.6543
|
---|
| 581 | $lki = $hlp[2];
|
---|
| 582 | $sys = $hlp[3];
|
---|
| 583 | push @{ $BIA{$lki}{$sys}{EPOCH} }, $epochSec;
|
---|
| 584 | push @{ $BIA{$lki}{$sys}{DATA} }, $hlp[4] + $hlp[5];
|
---|
| 585 | }
|
---|
[10090] | 586 | elsif ( $ln =~ /\bSATNUM\b/ ) { # 2015-09... SATNUM G 8
|
---|
[9597] | 587 | push ( @{ $SATNUM{ $hlp[2] } }, $hlp[3] );
|
---|
| 588 | }
|
---|
[10090] | 589 | elsif ( $ln =~ /\bTRP\b/ ) { # 2015-08... TRP 2.3803 +0.1009 +- 0.0324
|
---|
[10383] | 590 | push ( @TRP, $hlp[2] + $hlp[3] );
|
---|
[9597] | 591 | }
|
---|
[10585] | 592 | elsif ( $ln =~ /\bREC_CLK\b/) {
|
---|
| 593 | # 2024-10-20_03:57:30.000 RECCLK G 0.0000 -1.7861 +- 0.5268
|
---|
| 594 | $sys = $hlp[2];
|
---|
| 595 | push @{ $RECCLK{$sys}{EPOCH} }, $epochSec;
|
---|
| 596 | push @{ $RECCLK{$sys}{DATA} }, $hlp[3] + $hlp[4];
|
---|
| 597 | }
|
---|
[9597] | 598 | } # ----- next line -----
|
---|
| 599 |
|
---|
| 600 | $logMode && filePosition( $file, tell ($fh) ); # Remember pos for next read
|
---|
| 601 | close $fh;
|
---|
| 602 |
|
---|
[10383] | 603 | my $nof_epochs = scalar @EPOCH;
|
---|
[10090] | 604 | DEBUG( "$station: epochs:$nof_epochs, North displac.: "
|
---|
[9597] | 605 | . scalar @N
|
---|
| 606 | . ", East displac.: "
|
---|
| 607 | . scalar @E
|
---|
| 608 | . ", Up displac.: "
|
---|
| 609 | . scalar @U
|
---|
[10383] | 610 | . ", TRP:"
|
---|
| 611 | . scalar @TRP
|
---|
[10233] | 612 | );
|
---|
[10383] | 613 | if ( $nof_epochs != scalar @N ) { LOGDIE "number of epochs and residuals not equal\n" }
|
---|
| 614 | if ( $nof_epochs != scalar @TRP ) { LOGDIE "number of epochs and TRP not equal\n" }
|
---|
[10259] | 615 |
|
---|
[10585] | 616 |
|
---|
[10090] | 617 | if ( !$station ) { WARN "could not grep stationname from file: $file\n" }
|
---|
[10034] | 618 |
|
---|
[9597] | 619 | my %data = (
|
---|
[10383] | 620 | EPOCH => \@EPOCH,
|
---|
| 621 | N => \@N,
|
---|
| 622 | E => \@E,
|
---|
| 623 | U => \@U,
|
---|
| 624 | SATNUM => \%SATNUM,
|
---|
| 625 | TRP => \@TRP,
|
---|
[10585] | 626 | RECCLK => \%RECCLK,
|
---|
[10383] | 627 | RES => \%RES,
|
---|
| 628 | AMB => \%AMB,
|
---|
| 629 | ELE => \%ELE,
|
---|
| 630 | ION => \%ION,
|
---|
| 631 | BIA => \%BIA,
|
---|
[9597] | 632 | );
|
---|
| 633 |
|
---|
[9910] | 634 | return ( $station, \%data, 0 );
|
---|
[9597] | 635 | }
|
---|
| 636 |
|
---|
| 637 | # =============================================================================
|
---|
| 638 | # BncStillWorks ($bncConfFile)
|
---|
| 639 | # =============================================================================
|
---|
| 640 | # Checks if BNC is still working.
|
---|
| 641 | #
|
---|
| 642 | # BNC Jobs can still be alive (in processlist) but are not producing any more.
|
---|
| 643 | # This function checks if a BNC process is proper working.
|
---|
| 644 | #
|
---|
| 645 | # Param : $bncConfFile [required] path of BNC config file
|
---|
| 646 | # Return : true if BNC is still working otherwise false.
|
---|
| 647 | # =============================================================================
|
---|
| 648 | sub BncStillWorks {
|
---|
| 649 | my ($bncConfFile) = @_;
|
---|
| 650 |
|
---|
| 651 | my $timep = Time::Piece->new;
|
---|
| 652 |
|
---|
| 653 | # for safety if it is exatly at 00:00, add 30 sec
|
---|
| 654 | my $min_tmp = $timep->strftime("%M");
|
---|
| 655 | if ( $min_tmp =~ /00|15|30|45/ && $timep->strftime("%S") < 15 ) {
|
---|
| 656 | $timep += 30;
|
---|
| 657 | sleep 30;
|
---|
| 658 | }
|
---|
| 659 | my $yyyy = $timep->year;
|
---|
| 660 | my $yy = $timep->yy;
|
---|
| 661 | my $doy = sprintf "%03d", $timep->yday + 1;
|
---|
| 662 | my $hh = $timep->strftime("%H");
|
---|
| 663 | my $h = uc ( chr ( 65 + $hh ) );
|
---|
| 664 | my $min = $timep->min;
|
---|
| 665 | my $startmin;
|
---|
| 666 | if ( $min < 15 ) { $startmin = "00" }
|
---|
| 667 | elsif ( $min < 30 ) { $startmin = "15" }
|
---|
| 668 | elsif ( $min < 45 ) { $startmin = "30" }
|
---|
| 669 | elsif ( $min <= 59 ) { $startmin = "45" }
|
---|
| 670 | my $bncConf = parseConf($bncConfFile);
|
---|
| 671 | my $bncLogFileStub = $bncConf->{'General'}->{'logFile'};
|
---|
| 672 |
|
---|
| 673 | # BNC log file
|
---|
| 674 | # ------------
|
---|
| 675 | my $bncLogFile = "${bncLogFileStub}_" . $timep->strftime("%y%m%d"); # -> bnc.log_160425
|
---|
| 676 | unless ( -s $bncLogFile ) {
|
---|
| 677 | WARN("BNC logfile \"$bncLogFile\" is empty or does not exist");
|
---|
| 678 | return 0;
|
---|
| 679 | }
|
---|
| 680 |
|
---|
| 681 | # RINEX Obs Generation
|
---|
| 682 | # --------------------
|
---|
| 683 | if ( $bncConf->{'General'}->{'rnxPath'} ) {
|
---|
| 684 | my $rnxPath = $bncConf->{'General'}->{'rnxPath'};
|
---|
| 685 | $rnxPath =~ s/\/$//;
|
---|
| 686 |
|
---|
| 687 | # Write Rnx3 files (i.e. long Rnx3 filenames) 2: on ('rnxV3filenames' is deprecated since 2.12.8!!!)
|
---|
| 688 | my $writeRnxV3 = $bncConf->{'General'}->{'rnxV3'};
|
---|
| 689 | my $rnxIntr = $bncConf->{'General'}->{'rnxIntr'};
|
---|
| 690 | my $fileMask;
|
---|
| 691 |
|
---|
| 692 | if ($writeRnxV3) {
|
---|
| 693 | if ( $rnxIntr eq "1 hour" ) {
|
---|
| 694 | $fileMask = "*_S_${yyyy}${doy}${hh}??_01H_30S_?O.rnx";
|
---|
| 695 | }
|
---|
| 696 | elsif ( $rnxIntr eq "15 min" ) {
|
---|
| 697 | $fileMask = "*_S_${yyyy}${doy}${hh}${startmin}_15M_01S_?O.rnx";
|
---|
| 698 | }
|
---|
| 699 | else { # daily?
|
---|
| 700 | $fileMask = "*_S_${yyyy}${doy}????_01D_30S_?O.rnx"; # HRAG00ZAF_S_20191220000_01D_30S_MO.rnx
|
---|
| 701 | }
|
---|
| 702 | }
|
---|
| 703 | else { # Rnx2
|
---|
| 704 | if ( $rnxIntr eq "1 hour" ) {
|
---|
| 705 | $fileMask = "????${doy}${h}.${yy}O";
|
---|
| 706 | }
|
---|
| 707 | elsif ( $rnxIntr eq "15 min" ) {
|
---|
| 708 | $fileMask = "????${doy}${h}${startmin}.${yy}O";
|
---|
| 709 | }
|
---|
| 710 | else { # daily?
|
---|
| 711 | $fileMask = "????${doy}*.${yy}O";
|
---|
| 712 | }
|
---|
| 713 | }
|
---|
| 714 |
|
---|
| 715 | my @rnxFiles = glob "$rnxPath/$fileMask";
|
---|
| 716 | if ( scalar @rnxFiles < 1 ) {
|
---|
| 717 | ERROR("BNC does not create RINEX Obs files. (Filemask: \"$fileMask\" Path: $rnxPath)");
|
---|
| 718 |
|
---|
| 719 | #return 0;
|
---|
| 720 | }
|
---|
| 721 | }
|
---|
| 722 |
|
---|
| 723 | # RINEX Ephemerides Generation
|
---|
| 724 | # ----------------------------
|
---|
| 725 | if ( $bncConf->{'General'}->{'ephPath'} ) {
|
---|
| 726 | my $rnxPath = $bncConf->{'General'}->{'ephPath'};
|
---|
| 727 | $rnxPath =~ s/\/$//;
|
---|
| 728 | my $writeRnxV3 = $bncConf->{'General'}->{'ephV3'};
|
---|
| 729 | my $rnxIntr = $bncConf->{'General'}->{'ephIntr'};
|
---|
| 730 | my $fileMask;
|
---|
| 731 |
|
---|
| 732 | if ($writeRnxV3) {
|
---|
| 733 | if ( $rnxIntr eq "1 hour" ) {
|
---|
| 734 | $fileMask = "BRD?00WRD_S_${yyyy}${doy}${hh}00_01H_?N.rnx";
|
---|
| 735 | }
|
---|
| 736 | elsif ( $rnxIntr eq "15 min" ) {
|
---|
| 737 | $fileMask = "BRD?00WRD_S_${yyyy}${doy}${hh}${startmin}_15M_?N.rnx"; # BRDC00WRD_S_20191220900_15M_MN.rnx
|
---|
| 738 | }
|
---|
| 739 | else { # daily?
|
---|
| 740 | $fileMask = $fileMask = "BRD?00WRD_S_${yyyy}${doy}0000_01D_?N.rnx";
|
---|
| 741 | }
|
---|
| 742 | }
|
---|
| 743 | else { # Rnx2
|
---|
| 744 | $fileMask = "BRD?${doy}*.${yy}N";
|
---|
| 745 | }
|
---|
| 746 |
|
---|
| 747 | my @rnxFiles = glob "$rnxPath/$fileMask";
|
---|
| 748 | if ( scalar @rnxFiles < 1 ) {
|
---|
| 749 | ERROR("BNC does not create RINEX Nav files. (Filemask: \"$fileMask\" Path: $rnxPath)");
|
---|
| 750 |
|
---|
| 751 | #return 0;
|
---|
| 752 | }
|
---|
| 753 | }
|
---|
| 754 |
|
---|
| 755 | # Check jobs making PPP
|
---|
| 756 | # ---------------------
|
---|
| 757 | if ( $bncConf->{'PPP'}->{'corrMount'} && $bncConf->{'PPP'}->{'staTable'} ) {
|
---|
| 758 | my $timeOfLastCoo = `grep "NEU:" $bncLogFile | tail -1 | cut -d ' ' -f1,2`;
|
---|
| 759 | chomp $timeOfLastCoo;
|
---|
| 760 | if ( !$timeOfLastCoo ) {
|
---|
| 761 | ERROR "BNC does not compute coordinates";
|
---|
| 762 | return 0;
|
---|
| 763 | }
|
---|
| 764 |
|
---|
| 765 | my $tp = Time::Piece->strptime( $timeOfLastCoo, '%y-%m-%d %H:%M:%S' );
|
---|
| 766 | my $now = Time::Piece->new;
|
---|
| 767 | my $tdiff = $now - $tp;
|
---|
| 768 | if ( $tdiff > 1200 ) {
|
---|
| 769 | ERROR( "Last computed coordinates are " . $tdiff / 60 . " min old" );
|
---|
| 770 | return 0;
|
---|
| 771 | }
|
---|
| 772 | }
|
---|
| 773 |
|
---|
| 774 | # BNC works
|
---|
| 775 | return 1;
|
---|
| 776 | }
|
---|
| 777 |
|
---|
| 778 | 1; # End of Bnc
|
---|