# converts X-plane xgs landing speed plugin text-file log to xlsx format # appends to existing data after backup # TODO: format runway col data to zero-pad 1-9 use v5.28.0; # implicit strict/warnings + say use IO::All; use DateTime; use Date::Parse; use Data::Printer; # use_prototypes => 0; use FindBin qw($Bin); use Spreadsheet::Read; use Spreadsheet::XLSX; # for above to parse XSLX files use Encoding::FixLatin qw/fix_latin/; use lib '/home/raj/perl-lib'; use Local::WriteExcel; use Getopt::Long; my $dry_run = 0; # --dry-run|d - output regex, no file updates or backup GetOptions ( "dry-run|d" => \$dry_run, # flag ); # warn $dry_run; exit; #=============================================================================== my $src_file = '/data/X-Plane 11/Output/xgs_landing.log'; die "no such file $src_file\n" unless -e $src_file; #my $io = new IO::File; #open( $io, '<', $src_file ) || die $!; my $src = io($src_file); # count lines in src file: my @lines = $src->chomp->slurp; say 'log-file lines: ' . scalar @lines; # exit # xlsx file - output so we can use worksheet_name in Local::WriteExcel #------------------------------------------------------------------------------- my $xlfile = 'xgs_landing.xlsx'; die "no such file $xlfile\n" unless -e $xlfile; # read contents of existing spreadsheet [1] for inclusion in log data: my $sheet = ReadData($xlfile)->[1]; # rows() not exported so call as fully qualified method: my @rows = Spreadsheet::Read::rows($sheet); # p @rows; # AoA # get datetime of final entry: my $last_entry = do { my $date = join ' ', @{ $rows[-1] }[0,1]; # say $date; my $epoch = str2time($date); # p $epoch; DateTime->from_epoch(epoch => $epoch); }; # p $last_entry; exit; # remove 1st line (headers): shift @rows; # p @rows; exit; #------------------------------------------------------------------------------- # 11/17/21 12:10:06 A321 C-GTLU KBOS -0.981 m/s -193 fpm 3.7° pitch 1.496 G, Threshold 32, # Above: 69 ft / 21 m, Distance: 1745 ft / 532 m, from CL: -2 ft / -1 m / 0.5°, Firm landing # 2nd capture item (aircraft type) maybe blank due to missing acf/_ICAO in .acf file # ([ \S]+) to avoid acquiring strange line-ending char _x000D_ introduced by fix_latin(): my $re = q!(\d{2}:\d{2}:\d{2}) (\w+)? (\w\-?\w+) (\w{4}) \-\d.\d+ m/s \-(\d+) ! . q!fpm (-?\d+\.\d+)° pitch (\d+\.\d+) G, Threshold (\d{2}[LCR]?)\, Above: ! . q!(\d+) ft / \d+ m, Distance: (\d+) ft / \d+ m, from CL: -?(\d+) ft / ! . q!-?\d+ m / -?\d+\.\d+°, ([ \S]+)!; #------------------------------------------------------------------------------- parse_logfile() and exit if $dry_run; #------------------------------------------------------------------------------- { # backup existing xlsx file: my $id = DateTime->now->datetime; $id =~ s/://g; # remove from time component io($xlfile) > io( sprintf 'backups/%s.xlsx', $id ); } # create new xlsx file: my $xl = Local::WriteExcel->new( filename => $xlfile ); # p $xl->xl_object; exit; # headers: my @vars = qw( date time type reg icao fpm pitch g_force runway thr_height td_distance lateral rating ); # required order for headers and xlsx file cols: my @order = (0..4, 8..11, 6, 7, 5, 12); # say "@vars[@order]"; exit; $xl->write_bold_row([ @vars[@order] ]); # write existing data back: $xl->write_row($_) for @rows; { # new data from logfile: my @data = parse_logfile(); $xl->write_row($_) for @data; } # save output: $xl->save(); #=============================================================================== sub parse_logfile { my @data; LOGLINE: for ( @lines ) { # warn $_; # next; next LOGLINE if /Not on a runway/; # probably not serious landing attempt # replace invalid characters (degree-sign): my $ref = _escape($_); # p $ref; # get a datetime from American-style date: my $dt = _parse_date($ref); # say $date_time; # skip unless log entry more recent than xlsx file last entry: next LOGLINE unless $dt->epoch > $last_entry->epoch; # warn 'here'; my @vals = $ref =~ m!$re!; # p @vals; # last LOGLINE; say $ref and next LOGLINE unless @vals; # p @vals; # warn $_ for @vals; # RWDesign's Twin Otter does not have P acf/_ICAO entry in DHC6.afc file # so aircraft type is null, use registration to identify: $vals[1] = 'DHC6' if $vals[2] eq 'N244KR' && ! $vals[1]; # p @vals; # add date to start of array so we can use @order: unshift @vals, $dt->ymd; push @data, [ @vals[@order] ]; say "adding $dt data"; } return wantarray ? @data : \@data; } sub _escape { # substitute incompatible chars: my $str = shift; # p fix_latin($str); # takes mixed encoding input and produces UTF-8 output (fixes degree sign): fix_latin($str); } sub _parse_date { my $str = shift; # warn $str; my ($date_time) = $str =~ m!(\d{2,4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2})!; # p $date_time; my $epoch = str2time($date_time); # p $epoch; ' Date::Parse my $dt = DateTime->from_epoch(epoch => $epoch); # p $date_time; return $dt; }