source: ntrip/trunk/BNC/scripts/Bnc.pm@ 10837

Last change on this file since 10837 was 10809, checked in by stuerze, 3 months ago

minor changes

  • Property svn:keywords set to Header
File size: 28.0 KB
Line 
1package Bnc;
2
3# Perl utility functions for BNC
4#
5# Revision: $Header: trunk/BNC/scripts/Bnc.pm 10809 2026-02-09 09:45:42Z stuerze $
6
7use strict;
8use warnings;
9use File::Basename;
10use File::Spec::Functions qw(catfile);
11use File::Temp qw(tempfile);
12use Exporter;
13use Time::Piece 1.30;
14use PDL::Lite; # to avoid namespace pollution
15use PDL::Primitive;
16use 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# =============================================================================
29sub 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.
56sub 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#
73# Param : $logfile [required] Path of the BNC/scanRTCM logfile
74# $caAbbr [optional] caster name or abbreviation
75# $tmpPath [optional] write working files to this path
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# =============================================================================
83sub 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(
105"grep \"Received message type\" $logfile | sort -u > $casterMessTypeFile"
106 ) == 0
107 or ERROR "Fehler: $!";
108
109 # --------------------------------------------
110 # Get message types foreach station/mountpoint
111 # --------------------------------------------
112 my $cmd = "cat $casterMessTypeFile | awk '{print \$3, \$7}' | sort -u";
113 my @messTypes = `$cmd`; # ['DARX1: 1004\n','DARX1: 1006\n',... ]
114 if ( scalar @messTypes < 1 ) {
115 ERROR "Could not retrieve message types from file $casterMessTypeFile";
116 return;
117 }
118
119# Note: Skip first 1500 lines because BNC is weird here, all lines with same timestamp, buffer problem?
120
121# ------------------------------------------------------
122# Guess repetition rate for each mountpoint/message-type
123# ------------------------------------------------------
124# For that get the first $minSamples appearances of mountp with same message type
125# and build the differences between them
126 my $minSamples = 4;
127 foreach my $mpType (@messTypes) {
128 chomp $mpType; # 'AUBG3: 1004'
129 my ( $mp, $mt ) = split( /:\s*/, $mpType );
130 my $cmd = "grep \"$mp: Received message type $mt\" $logfile";
131 my @rows = `$cmd`;
132 if ( scalar @rows < 1 ) {
133 ERROR
134"Could not retrieve message types for $mpType from file $casterMessTypeFile";
135 next;
136 }
137
138 if ( scalar @rows <= $minSamples ) {
139 WARN
140 "Could not guess repetition rate for message type $mpType: only "
141 . scalar @rows
142 . " matches found";
143 next;
144 }
145
146 my $repRate = _computeMessTypRepetitonRate( \@rows, $caAbbr );
147 if ($repRate) {
148 $mpType .= '(' . $repRate . ')';
149 }
150 } # ----- end foreach messageType -----
151
152#if ( unlink $casterMessTypeFile ) { TRACE "Removed file [$casterMessTypeFile]" }
153
154 # Return an HASH OF ARRAYS with mp as key and the list of messTypes as value
155 my %messTypesHash;
156 foreach (@messTypes) {
157 my @ele = split( ': ', $_ );
158 my $mp = shift @ele;
159 push( @{ $messTypesHash{$mp} }, shift @ele );
160 }
161
162 return \%messTypesHash;
163}
164
165# =============================================================================
166# _computeMessTypRepetitonRate
167# =============================================================================
168# Guess repetition rate of message types
169#
170# Param : $firstMatches [required] Array-Ref with first appearances of station
171# and mess type. Complete logfile lines, e.g.
172# '13-07-04 17:38:26 BRUX0: Received message type 1004 '
173# $caAbbr [optional] caster abbreviation
174# Return : repetition rate in secs, (median value)
175# =============================================================================
176sub _computeMessTypRepetitonRate {
177 my ( $rows, $caAbbr ) = @_;
178
179 my $maxGap = 600; # if gap > 10min then we guess it is a new scan
180
181 $rows->[0] =~ /.{18}([a-x0-9]+): Received message type (\d+)/i;
182 my $stat = $1;
183 my $mestp = $2;
184
185 # Create list of unix timestamps
186 my @scans;
187 my $scan = 0;
188 my ( $prevTime, $deltaT ) = ( 0, 0 );
189 foreach (@$rows) {
190 my $uxtime = date2unix( substr( $_, 0, 17 ) );
191 if ( !$uxtime ) {
192 ERROR "Could not parse date from line $_";
193 next;
194 }
195 $deltaT = $uxtime - $prevTime;
196 next if ( $deltaT == 0 ); # e.g. eph 1019,1020 one message for each sat.
197 next if ( $deltaT <= 1 && $mestp =~ /10(19|20|42|43|44|45|46)|63/ );
198 if ( $prevTime && $deltaT > $maxGap ) {
199 $scan++;
200 }
201 push( @{ $scans[$scan] }, $uxtime );
202 $prevTime = $uxtime;
203 }
204
205 my @repRates;
206 my $highest_nof_diffs = 0;
207 foreach (@scans) {
208 my @timestamps = @{$_};
209
210 # Compute the differences
211 my @diffs;
212 for ( my $i = 1 ; $i <= $#timestamps ; $i++ ) {
213 push( @diffs, $timestamps[$i] - $timestamps[ $i - 1 ] );
214 }
215 my $nof_diffs = scalar @diffs;
216
217 if ( $nof_diffs < 2 ) {
218 WARN("$stat: $mestp: only $nof_diffs diffs");
219 next;
220 }
221
222 my ( $mean, $prms, $median, $min, $max, $adev, $rms_n ) =
223 stats( pdl \@diffs );
224 $mean = sprintf( "%.0f", $mean );
225 $rms_n = sprintf( "%.02f", $rms_n );
226 print $stat, ": ", $mestp, ": ", join( ' ', @diffs ),
227 "[Sig: $mean, $rms_n]\n";
228
229 if ( $rms_n > 10 ) {
230 WARN("$stat: $mestp: RMS too high: $rms_n");
231 next;
232 }
233
234 # get the most frequent value
235 my %counti = ();
236 $counti{$_}++ foreach (@diffs);
237 my ( $ni, $mfv ) = ( 0, 0 );
238 while ( my ( $k, $v ) = each %counti ) {
239 if ( $v > $ni ) {
240 $ni = $v;
241 $mfv = $k;
242 }
243 }
244
245 my $rounded_val = $mfv; # init
246 foreach ( ( 1, 5, 10, 15, 30, 60, 120, 150, 300 ) )
247 { # most likely values
248 my $mdiff = abs( $mfv - $_ );
249 if ( $mdiff <= 2 ) {
250 $rounded_val = $_;
251 }
252 }
253 push( @repRates, [ $rounded_val, $nof_diffs ] );
254
255 if ( $nof_diffs > $highest_nof_diffs ) {
256 $highest_nof_diffs = $nof_diffs;
257 }
258 } # ----- end foreach scan -----
259
260 my @mostLikelyRates = grep { $_->[1] == $highest_nof_diffs } @repRates;
261 my $mostLikelyRate = $mostLikelyRates[0]->[0];
262 foreach (@repRates) {
263 if ( abs( $_->[0] - $mostLikelyRate ) > 2 ) {
264 ERROR
265"$stat: $caAbbr: $mestp: repetition rates from different scans differ: $mostLikelyRate $_->[0]";
266 if ( scalar @repRates == 2 ) {
267 return;
268 }
269 }
270 }
271
272 return $mostLikelyRate;
273}
274
275# =============================================================================
276# parseConfig ($confFile)
277# =============================================================================
278# Parse the BNC config file.
279#
280# Param : $confFile [required] BNC config file
281# Return : Hash with configuration on success, otherwise undef
282# Usage : $bncConf = parseConf($bncConfFile);
283# $corrMount = $bncConf->{'PPP'}->{'corrMount'};
284# =============================================================================
285sub parseConfig {
286 my ($confFile) = @_;
287
288 -s $confFile || LOGDIE "BNC config file \"$confFile\" does not exist\n";
289 TRACE "Parse BNC config file $confFile";
290 open( my $INP, '<', $confFile )
291 || die "Could not open file '$confFile': $!";
292 my @confLines = <$INP>;
293 close($INP);
294
295 my %conf;
296 my $section; # [General], [PPP]
297 foreach (@confLines) {
298 chomp;
299 s/#.*//; # entfernt Kommentare
300 s/^\s*//; # whitespace am Anfang entfernen
301 s/\s+$//; # entfernt alle whitespaces am Ende
302 next unless length;
303 if ( $_ =~ /\[(\S+)\]/ ) { $section = $1 }
304 next if ( !$section );
305 my ( $key, $val ) = split( /\s*=\s*/, $_, 2 );
306 if ( !defined $val ) { $val = "" }
307
308 if ( $key eq "mountPoints" ) {
309
310 # Simple parsing
311 $val =~ s/^\/\///;
312 my @mpts = split( /,\s?\/{2}/, $val );
313 $conf{$section}->{$key} = \@mpts;
314
315 # Extended parsing
316 my @mpts_def = ();
317 foreach (@mpts) {
318
319 # user:passwd@igs-ip.net:2101/ASPA0 RTCM_3.0 ASM -14.33 189.28 no 1
320 if ( $_ =~
321/^([\w-]+):(.+[^@])@([\w\.-]+):(\d{3,5})\/([-\w]+) ([\w\.]+) ?(\w{3})? ([\+\-\d\.]+) ([\+\-\d\.]+) no (\w+)/i
322 )
323 {
324 push(
325 @mpts_def,
326 {
327 caster => $3,
328 port => $4,
329 mp => $5,
330 ntripVers => $10
331 }
332 );
333 }
334 else {
335 ERROR "$confFile: Could not parse mountPoints string $_";
336 }
337 }
338 $conf{$section}->{'mountPoints_parsed'} = \@mpts_def;
339 }
340 elsif ( $key eq "cmbStreams" ) {
341 my @cmbStrs = split( /\s*,\s*/, $val );
342 foreach (@cmbStrs) {
343 s/"//g;
344 s/\s+$//; # entfernt alle whitespaces am Ende
345 }
346 $conf{$section}->{$key} = \@cmbStrs;
347 }
348 else { $conf{$section}->{$key} = $val }
349 }
350
351 my @nofPar = keys %conf;
352 if ( scalar @nofPar < 1 ) {
353 ERROR "No parameter found in BNC conf \"$confFile\"";
354 return;
355 }
356 return \%conf;
357}
358
359# =============================================================================
360# parseLogfile ($file, $sampling, $goBackSecs, $logMode )
361# =============================================================================
362# Parse BNCs' logfile
363#
364# Param : $file [required] BNC logfile
365# $sampling [optional] sampling rate for logfile
366# $logMode [optional] Flag. If set, remember the position of the file-read
367# for the next read. Default: off
368# Return : \%data
369# =============================================================================
370sub parseLogfile {
371 my $file = shift;
372 my $sampling = shift // 1;
373 my $logMode = shift // 0;
374
375 open( my $fh, "<", $file ) || LOGDIE "Could not open file $file: $!\n";
376
377 # Goto last position from last read
378 #my $fPos = filePosition($file);
379 #TRACE "Current file pos: $fPos";
380 $logMode && seek( $fh, &Common::filePosition($file), 0 );
381
382 #$logMode && seek ( $fh, $fPos, 0 );
383 my $ln = "";
384 my ( @hlp, @epochs, @latencies, @restarts );
385 my $rec = {};
386 while (<$fh>) {
387 chomp( $ln = $_ );
388 $rec = {};
389
390 if ( $ln =~ /\bNEU/ ) { # NEU displacements
391 @hlp = split( /\s+/, $ln );
392 my $tp = Time::Piece->strptime( substr( $hlp[2], 0, 19 ),
393 '%Y-%m-%d_%H:%M:%S' );
394
395 if ( $hlp[14] eq '-nan'
396 || $hlp[15] eq '-nan'
397 || $hlp[16] eq '-nan' ) {
398 WARN("$hlp[2] $hlp[3]: NEU displacements are NAN");
399 next;
400 }
401
402 #DEBUG ($tp->epoch, $hlp[3]);
403 push(
404 @epochs,
405 {
406 time => $tp->epoch,
407 site => $hlp[3],
408 dN => $hlp[14],
409 dE => $hlp[15],
410 dU => $hlp[16],
411 TRP => $hlp[18] + $hlp[19],
412 }
413 );
414 }
415 elsif ( index( $ln, "latency", 25 ) > 1 ) {
416
417# DEBUG ($ln);
418# 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
419# 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
420 @hlp = split( /\s+/, $ln );
421
422 # old latency log format
423 if ( $hlp[2] =~ /:$/ ) {
424 splice @hlp, 3, 0, 'Placeholder:';
425 $hlp[2] =~ s/:$//;
426 }
427 $hlp[3] =~ s/:$//;
428
429 my $tp =
430 Time::Piece->strptime( "$hlp[0] $hlp[1]", '%y-%m-%d %H:%M:%S' );
431 $rec = {
432 time => $tp->epoch,
433 mp => $hlp[2],
434 meanLat => $hlp[6] + 0.0,
435 epochs => int( $hlp[14] ),
436 type => $hlp[3]
437 };
438
439 # Unter bestimmten Bedingungen werden die gaps nicht rausgeschrieben!
440 if ( $ln =~ /gaps/ ) {
441 $rec->{'gaps'} = int( $hlp[16] );
442 }
443
444 push( @latencies, $rec );
445 }
446 elsif ( index( $ln, "Start BNC" ) > 1 ) {
447
448 # 17-06-13 07:06:58 ========== Start BNC v2.12.3 (LINUX) ==========
449 @hlp = split( /\s+/, $ln );
450 my $tp =
451 Time::Piece->strptime( "$hlp[0] $hlp[1]", '%y-%m-%d %H:%M:%S' );
452 push(
453 @restarts,
454 {
455 time => $tp->epoch,
456 bncvers => $hlp[5]
457 }
458 );
459 }
460
461 } # ----- next line -----
462
463 $logMode
464 && &Common::filePosition( $file, tell($fh) ); # Remember pos for next read
465 close $fh;
466
467 # Sampling must be done afterwords, for each station separated!
468 my @epochs_sampled;
469 my @sites = map { $_->{'site'} } @epochs;
470
471 #@sites = uniq @sites;
472 my %hlp1 = ();
473 @sites = grep { !$hlp1{$_}++ } @sites;
474 foreach my $s (@sites) {
475 my $epoch_selected = 0;
476 foreach my $rec (@epochs) {
477 next if ( $rec->{'site'} ne $s );
478 if ( $rec->{'time'} - $epoch_selected >= $sampling ) {
479 push( @epochs_sampled, $rec );
480 $epoch_selected = $rec->{'time'};
481 }
482 }
483 }
484
485 my %data = (
486 EPOCHS => \@epochs_sampled,
487 LATENCIES => \@latencies,
488 RESTARTS => \@restarts
489 );
490
491 return \%data;
492}
493
494# =============================================================================
495# parsePPPLogfile ($file, $sampling, $goBackSecs, $logMode )
496# =============================================================================
497# Parse BNCs' PPP station logfile
498#
499# Param : $file [required] BNC PPP station
500# $sampling [optional] sampling rate for logfile
501# $goBackSecs [optional] go back that seconds from now in logfile
502# $logMode [optional] Flag. If set, remember the position of the file-read
503# for the next read. Default: off
504# Return : $dateStr, $station, $ssrData \%data
505# =============================================================================
506sub parsePPPLogfile {
507 my $file = shift;
508 my $sampling = shift // 1;
509 my $goBackSecs = shift // 0;
510 my $logMode = shift // 0;
511
512 if ($logMode) { $goBackSecs = 0 }
513
514 my $startSec;
515 if ($goBackSecs) {
516 $startSec = time() - $goBackSecs;
517 }
518 my $epo_ppp;
519 my $old_epochSec = 0;
520 my $epochSec = 0;
521 my $epochDiff = 0;
522
523 my ( @EPOCH_PPP);
524 my ( @hlp, @N, @E, @U, @ISFIX, @NUMFIX, @TRP );
525 my ( %SATNUM, %RECCLK, %AMB, %RES, %ELE, %ION, %BIA );
526 my ( $date, $station, $lki, $sys, $sat, $amb );
527
528 my ( @EPOCH_SSR);
529 my ( %CLKCORR);
530 my ( %YAW_SSR);
531 my ( %YAW_DEF);
532 my ( %CODEBIAS);
533 my ( %PHASEBIAS);
534 my ( %JUMPCOUNT);
535 my $epochSsrSec = 0;
536 my $ssrData;
537 my $dateStr;
538
539 open( my $fh, "<", $file ) || LOGDIE "Could not open file $file: $!\n";
540
541 # Goto last position from last read
542 #my $fPos = filePosition($file);
543 #TRACE "Current file pos: $fPos";
544 $logMode && seek( $fh, filePosition($file), 0 );
545
546 #$logMode && seek ( $fh, $fPos, 0 );
547 my $ln = "";
548 while (<$fh>) {
549 chomp( $ln = $_ );
550 if ( $ln =~ /\bof Epoch\b/ ) {
551 # PPP of Epoch 2015-08-27_14:00:15.000
552 if ( $ln =~ /PPP of Epoch (\d{4}-\d{2}-\d{2}_\d{2}:\d{2}:\d{2})\.\d+/ ) {
553 $epo_ppp = $1; #print "$epo_ppp\n";
554 @hlp = split( /\s+/, $ln );
555 $ssrData = $hlp[5]; #print "$ssrData\n";
556 $dateStr = substr( $hlp[3], 0, 10 );
557
558 }
559 else {
560 ERROR "strange ppp line: \"$ln\""; next
561 }
562
563 my $tp = Time::Piece->strptime( $epo_ppp, '%Y-%m-%d_%H:%M:%S' );
564 $epochSec = $tp->epoch();
565 $epochDiff = $epochSec - $old_epochSec;
566 next;
567 }
568
569 next if ( !$epo_ppp );
570 next if ( defined $startSec && $epochSec < $startSec );
571 next if ( $epochDiff && $epochDiff < $sampling );
572
573 @hlp = split( /\s+/, $ln );
574
575 if ( $ln =~ /\bdN\b/ ) {
576 #2025-11-29_16:55:09.000 WTZR00DEU0 X = 4075580.2482 +- 0.0500 Y = 931854.1736 +- 0.0314 Z = 4801568.3044 +- 0.0591 dN = 0.0074 +- 0.0407 dE = 0.0895 +- 0.0284 dU = -0.0072 +- 0.0672 fix 96 %
577 push( @EPOCH_PPP, $epochSec );
578 $old_epochSec = $epochSec;
579 $date = substr( $hlp[0], 0, 9 );
580 $station = $hlp[1];
581 my $numFix = 0;
582 my $isFix;
583 my $strFix = "fix";
584 if ( $hlp[19] eq '-nan'
585 || $hlp[24] eq '-nan'
586 || $hlp[29] eq '-nan' ) {
587 WARN("$hlp[0] $station: NEU displacements are NAN");
588 }
589 push @N, $hlp[19];
590 push @E, $hlp[24];
591 push @U, $hlp[29];
592
593 if ( $hlp[32] eq $strFix ) {
594 $isFix = 1;
595 $numFix = int( $hlp[33] );
596 }
597 else {
598 $isFix = 0;
599 $numFix = 0;
600 }
601 push @ISFIX, $isFix;
602 push @NUMFIX, $numFix;
603 }
604 elsif ( ( $ln =~ /\bAMB\b/ ) && ( $ln !~ /RESET/ ) ) { # 2015-08... AMB lIF G04 253.0000 -8.9924 +- 1.7825 el = 22.03 epo = 86
605 $lki = $hlp[2];
606 $sat = $hlp[3];
607 $sys = substr( $sat, 0, 1 );
608 $amb = $hlp[4] + $hlp[5];
609 push @{ $AMB{$lki}{$sys}{$sat}{EPOCH_PPP} }, $epochSec;
610 push @{ $AMB{$lki}{$sys}{$sat}{DATA} }, $amb;
611 push @{ $AMB{$lki}{$sys}{$sat}{NUMEPO} }, $hlp[13];
612 push @{ $ELE{$sys}{$sat}{EPOCH_PPP} }, $epochSec;
613 push @{ $ELE{$sys}{$sat}{DATA} }, $hlp[10];
614 }
615 elsif ( $ln =~ /\bRES\b/ && $ln !~ /Neglected/ ) { # 2015-08... RES lIF G30 -0.0076
616 $sat = $hlp[3];
617 $lki = $hlp[2];
618 $sys = substr( $sat, 0, 1 );
619
620 #print "$epo $lki $sys $sat $res\n";
621 push @{ $RES{$lki}{$sys}{$sat}{EPOCH_PPP} }, $epochSec;
622 push @{ $RES{$lki}{$sys}{$sat}{DATA} }, $hlp[4];
623 }
624 elsif ( ( $ln =~ /\bION\b/ ) && ( $ln !~ /RESET/ ) ) { # 2018-12-01_20:37:58.000 ION G02 0.0000 -0.3277 +- 2.4663
625 $sat = $hlp[2];
626 $sys = substr( $sat, 0, 1 );
627 push @{ $ION{$sys}{$sat}{EPOCH_PPP} }, $epochSec;
628 push @{ $ION{$sys}{$sat}{DATA} }, $hlp[4];
629 }
630 elsif ( ( $ln =~ /\bBIA\b/ ) && ( $ln !~ /RESET/ ) ) { # 2020-12-09_00:55:19.000 BIA c1 G 0.0000 +2.5149 +- 9.6543
631 $lki = $hlp[2];
632 $sys = $hlp[3];
633 push @{ $BIA{$lki}{$sys}{EPOCH_PPP} }, $epochSec;
634 push @{ $BIA{$lki}{$sys}{DATA} }, $hlp[4] + $hlp[5];
635 }
636 elsif ( $ln =~ /\bSATNUM\b/ ) { # 2015-09... SATNUM G 8
637 push( @{ $SATNUM{ $hlp[2] } }, $hlp[3] );
638 }
639 elsif ( $ln =~ /\bTRP\b/ ) { # 2015-08... TRP 2.3803 +0.1009 +- 0.0324
640 push( @TRP, $hlp[2] + $hlp[3] );
641 }
642 elsif ( $ln =~ /\bREC_CLK\b/ ) { # 2024-10-20_03:57:30.000 RECCLK G 0.0000 -1.7861 +- 0.5268
643 $sys = $hlp[2];
644 push @{ $RECCLK{$sys}{EPOCH_PPP} }, $epochSec;
645 push @{ $RECCLK{$sys}{DATA} }, $hlp[3] + $hlp[4];
646 }
647 ##################
648 # SSR parameters #
649 ##################
650 elsif ( $ln =~ /\bclkCorr\b/ && @hlp > 3) {
651 # clkCorr 2025-11-17_13:50:45.000 C45 -18.576
652 if ( $ln =~ /clkCorr (\d{4}-\d{2}-\d{2}_\d{2}:\d{2}:\d{2})\.\d+/ ) {
653 $epochSsrSec = $1; #print "$epochSsrSec\n";
654 }
655 else {
656 ERROR "strange ssr line: \"$ln\""; next
657 }
658 my $tp = Time::Piece->strptime( $epo_ppp, '%Y-%m-%d_%H:%M:%S' );
659 $epochSsrSec = $tp->epoch();
660 $date = substr( $hlp[0], 0, 9 );
661 $sat = $hlp[2];
662 $sys = substr( $sat, 0, 1 );
663 push @{ $CLKCORR{$sys}{$sat}{EPOCH_SSR} }, $epochSsrSec;
664 push @{ $CLKCORR{$sys}{$sat}{DATA} }, $hlp[3];
665 }
666 elsif ( $ln =~ /\byawAngle\b/&& @hlp > 3 ) {
667 # yawAngle 2025-11-17_13:50:40.000 G06 218.672 136.115
668 if ( $ln =~ /yawAngle (\d{4}-\d{2}-\d{2}_\d{2}:\d{2}:\d{2})\.\d+/ ) {
669 $epochSsrSec = $1; #print "$epochSsrSec\n";
670 }
671 else {
672 ERROR "strange ssr line: \"$ln\""; next
673 }
674 my $tp = Time::Piece->strptime( $epo_ppp, '%Y-%m-%d_%H:%M:%S' );
675 $epochSsrSec = $tp->epoch();
676 $sat = $hlp[2];
677 $sys = substr( $sat, 0, 1 );
678 push @{ $YAW_SSR{$sat}{EPOCH_SSR} }, $epochSsrSec;
679 push @{ $YAW_SSR{$sat}{DATA} }, $hlp[3];
680 push @{ $YAW_DEF{$sat}{EPOCH_SSR} }, $epochSsrSec;
681 push @{ $YAW_DEF{$sat}{DATA} }, $hlp[4];
682 }
683 elsif ( $ln =~ /\bcodeBias\b/ && @hlp > 3) {
684 #codeBias 2025-11-17_13:50:35.000 C45 1P 12.140 2I 13.330 5P 21.740 6I 20.190
685 if ( $ln =~ /codeBias (\d{4}-\d{2}-\d{2}_\d{2}:\d{2}:\d{2})\.\d+/ ) {
686 $epochSsrSec = $1; #print "$epochSsrSec\n";
687 }
688 else {
689 ERROR "strange ssr line: \"$ln\""; next
690 }
691 my $tp = Time::Piece->strptime( $epo_ppp, '%Y-%m-%d_%H:%M:%S' );
692 my ($num_bias, $bias_type, $bias_value);
693 $epochSsrSec = $tp->epoch();
694 $sat = $hlp[2];
695 $sys = substr( $sat, 0, 1 );
696 my $hlpSize = @hlp;
697 for (my $i=3; $i < $hlpSize; $i++) {
698 $bias_type = $hlp[$i++];
699 $bias_value = $hlp[$i];
700 push @{ $CODEBIAS{$bias_type}{$sys}{$sat}{EPOCH_SSR} }, $epochSsrSec;
701 push @{ $CODEBIAS{$bias_type}{$sys}{$sat}{DATA} }, $bias_value;
702 }
703 }
704 elsif ( $ln =~ /\bphaseBias\b/ && @hlp > 3) {
705 #phaseBias 2025-11-17_13:50:55.000 G02 315.00 0.000 1C 1 x 0.291 2W 1 x 0.408
706 if ( $ln =~ /phaseBias (\d{4}-\d{2}-\d{2}_\d{2}:\d{2}:\d{2})\.\d+/ ) {
707 $epochSsrSec = $1; #print "$epochSsrSec\n";
708 }
709 else {
710 ERROR "strange ssr line: \"$ln\""; next
711 }
712 my $tp = Time::Piece->strptime( $epo_ppp, '%Y-%m-%d_%H:%M:%S' );
713 my ($num_bias, $bias_type, $bias_value, $jump_count, $int_flag);
714 $epochSsrSec = $tp->epoch();
715 $sat = $hlp[2];
716 $sys = substr( $sat, 0, 1 );
717 my $hlpSize = @hlp;
718 for (my $i=5; $i < $hlpSize; $i++) {
719 $bias_type = $hlp[$i++]; #print "$sat type: $bias_type";
720 $jump_count = $hlp[$i++]; #print " count: $jump_count";
721 $int_flag = $hlp[$i++]; #print " flag: $int_flag";
722 $bias_value = $hlp[$i]; #print " bias: $bias_value \n";
723 push @{ $PHASEBIAS{$bias_type}{$sys}{$sat}{EPOCH_SSR} }, $epochSsrSec;
724 push @{ $JUMPCOUNT{$bias_type}{$sys}{$sat}{EPOCH_SSR} }, $epochSsrSec;
725 push @{ $PHASEBIAS{$bias_type}{$sys}{$sat}{DATA} }, $bias_value;
726 push @{ $JUMPCOUNT{$bias_type}{$sys}{$sat}{DATA} }, $jump_count;
727 }
728 }
729 } # ----- next line -----
730
731 $logMode && filePosition( $file, tell($fh) ); # Remember pos for next read
732 close $fh;
733
734 my $nof_epochs = scalar @EPOCH_PPP;
735 DEBUG( "$station: epochs:$nof_epochs, North displac.: "
736 . scalar @N
737 . ", East displac.: "
738 . scalar @E
739 . ", Up displac.: "
740 . scalar @U
741 . ", isfix: "
742 . scalar @ISFIX
743 . ", numfix: "
744 . scalar @NUMFIX
745 . ", TRP:"
746 . scalar @TRP );
747 if ( $nof_epochs != scalar @N ) {
748 LOGDIE "number of epochs and residuals not equal\n";
749 }
750 if ( $nof_epochs != scalar @TRP ) {
751 LOGDIE "number of epochs and TRP not equal\n";
752 }
753
754 if ( !$station ) { WARN "could not grep stationname from file: $file\n" }
755
756 my %data = (
757 EPOCH_PPP => \@EPOCH_PPP,
758 N => \@N,
759 E => \@E,
760 U => \@U,
761 ISFIX => \@ISFIX,
762 NUMFIX => \@NUMFIX,
763 TRP => \@TRP,
764 SATNUM => \%SATNUM,
765 RECCLK => \%RECCLK,
766 RES => \%RES,
767 AMB => \%AMB,
768 ELE => \%ELE,
769 ION => \%ION,
770 BIA => \%BIA,
771 EPOCH_SSR => \@EPOCH_SSR,
772 CLKCORR => \%CLKCORR,
773 YAW_SSR => \%YAW_SSR,
774 YAW_DEF => \%YAW_DEF,
775 CODEBIAS => \%CODEBIAS,
776 PHASEBIAS => \%PHASEBIAS,
777 JUMPCOUNT => \%JUMPCOUNT,
778 );
779
780 return ($dateStr, $station, $ssrData, \%data, 0 );
781}
782
783# =============================================================================
784# BncStillWorks ($bncConfFile)
785# =============================================================================
786# Checks if BNC is still working.
787#
788# BNC Jobs can still be alive (in processlist) but are not producing any more.
789# This function checks if a BNC process is proper working.
790#
791# Param : $bncConfFile [required] path of BNC config file
792# Return : true if BNC is still working otherwise false.
793# =============================================================================
794sub BncStillWorks {
795 my ($bncConfFile) = @_;
796
797 my $timep = Time::Piece->new;
798
799 # for safety if it is exatly at 00:00, add 30 sec
800 my $min_tmp = $timep->strftime("%M");
801 if ( $min_tmp =~ /00|15|30|45/ && $timep->strftime("%S") < 15 ) {
802 $timep += 30;
803 sleep 30;
804 }
805 my $yyyy = $timep->year;
806 my $yy = $timep->yy;
807 my $doy = sprintf "%03d", $timep->yday + 1;
808 my $hh = $timep->strftime("%H");
809 my $h = uc( chr( 65 + $hh ) );
810 my $min = $timep->min;
811 my $startmin;
812 if ( $min < 15 ) { $startmin = "00" }
813 elsif ( $min < 30 ) { $startmin = "15" }
814 elsif ( $min < 45 ) { $startmin = "30" }
815 elsif ( $min <= 59 ) { $startmin = "45" }
816 my $bncConf = parseConf($bncConfFile);
817 my $bncLogFileStub = $bncConf->{'General'}->{'logFile'};
818
819 # BNC log file
820 # ------------
821 my $bncLogFile =
822 "${bncLogFileStub}_" . $timep->strftime("%y%m%d"); # -> bnc.log_160425
823 unless ( -s $bncLogFile ) {
824 WARN("BNC logfile \"$bncLogFile\" is empty or does not exist");
825 return 0;
826 }
827
828 # RINEX Obs Generation
829 # --------------------
830 if ( $bncConf->{'General'}->{'rnxPath'} ) {
831 my $rnxPath = $bncConf->{'General'}->{'rnxPath'};
832 $rnxPath =~ s/\/$//;
833
834# Write Rnx3 files (i.e. long Rnx3 filenames) 2: on ('rnxV3filenames' is deprecated since 2.12.8!!!)
835 my $writeRnxV3 = $bncConf->{'General'}->{'rnxV3'};
836 my $rnxIntr = $bncConf->{'General'}->{'rnxIntr'};
837 my $fileMask;
838
839 if ($writeRnxV3) {
840 if ( $rnxIntr eq "1 hour" ) {
841 $fileMask = "*_S_${yyyy}${doy}${hh}??_01H_30S_?O.rnx";
842 }
843 elsif ( $rnxIntr eq "15 min" ) {
844 $fileMask = "*_S_${yyyy}${doy}${hh}${startmin}_15M_01S_?O.rnx";
845 }
846 else { # daily?
847 $fileMask = "*_S_${yyyy}${doy}????_01D_30S_?O.rnx"
848 ; # HRAG00ZAF_S_20191220000_01D_30S_MO.rnx
849 }
850 }
851 else { # Rnx2
852 if ( $rnxIntr eq "1 hour" ) {
853 $fileMask = "????${doy}${h}.${yy}O";
854 }
855 elsif ( $rnxIntr eq "15 min" ) {
856 $fileMask = "????${doy}${h}${startmin}.${yy}O";
857 }
858 else { # daily?
859 $fileMask = "????${doy}*.${yy}O";
860 }
861 }
862
863 my @rnxFiles = glob "$rnxPath/$fileMask";
864 if ( scalar @rnxFiles < 1 ) {
865 ERROR(
866"BNC does not create RINEX Obs files. (Filemask: \"$fileMask\" Path: $rnxPath)"
867 );
868
869 #return 0;
870 }
871 }
872
873 # RINEX Ephemerides Generation
874 # ----------------------------
875 if ( $bncConf->{'General'}->{'ephPath'} ) {
876 my $rnxPath = $bncConf->{'General'}->{'ephPath'};
877 $rnxPath =~ s/\/$//;
878 my $writeRnxV3 = $bncConf->{'General'}->{'ephV3'};
879 my $rnxIntr = $bncConf->{'General'}->{'ephIntr'};
880 my $fileMask;
881
882 if ($writeRnxV3) {
883 if ( $rnxIntr eq "1 hour" ) {
884 $fileMask = "BRD?00WRD_S_${yyyy}${doy}${hh}00_01H_?N.rnx";
885 }
886 elsif ( $rnxIntr eq "15 min" ) {
887 $fileMask =
888 "BRD?00WRD_S_${yyyy}${doy}${hh}${startmin}_15M_?N.rnx"
889 ; # BRDC00WRD_S_20191220900_15M_MN.rnx
890 }
891 else { # daily?
892 $fileMask = $fileMask =
893 "BRD?00WRD_S_${yyyy}${doy}0000_01D_?N.rnx";
894 }
895 }
896 else { # Rnx2
897 $fileMask = "BRD?${doy}*.${yy}N";
898 }
899
900 my @rnxFiles = glob "$rnxPath/$fileMask";
901 if ( scalar @rnxFiles < 1 ) {
902 ERROR(
903"BNC does not create RINEX Nav files. (Filemask: \"$fileMask\" Path: $rnxPath)"
904 );
905
906 #return 0;
907 }
908 }
909
910 # Check jobs making PPP
911 # ---------------------
912 if ( $bncConf->{'PPP'}->{'corrMount'} && $bncConf->{'PPP'}->{'staTable'} ) {
913 my $timeOfLastCoo =
914 `grep "NEU:" $bncLogFile | tail -1 | cut -d ' ' -f1,2`;
915 chomp $timeOfLastCoo;
916 if ( !$timeOfLastCoo ) {
917 ERROR "BNC does not compute coordinates";
918 return 0;
919 }
920
921 my $tp = Time::Piece->strptime( $timeOfLastCoo, '%y-%m-%d %H:%M:%S' );
922 my $now = Time::Piece->new;
923 my $tdiff = $now - $tp;
924 if ( $tdiff > 1200 ) {
925 ERROR(
926 "Last computed coordinates are " . $tdiff / 60 . " min old" );
927 return 0;
928 }
929 }
930
931 # BNC works
932 return 1;
933}
934
9351; # End of Bnc
Note: See TracBrowser for help on using the repository browser.