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

Last change on this file since 9747 was 9597, checked in by wiese, 2 years ago

ADD: Perl utility scripts

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