| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Ham::WSJTX::Logparse; |
|
2
|
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
12502
|
use 5.006; |
|
|
1
|
|
|
|
|
3
|
|
|
4
|
1
|
|
|
1
|
|
3
|
use strict; |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
14
|
|
|
5
|
1
|
|
|
1
|
|
3
|
use warnings; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
509
|
|
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=head1 NAME |
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
Ham::WSJTX::Logparse - Parses ALL.TXT log files from Joe Taylor K1JT's WSJT-X, to extract CQ and calling station |
|
10
|
|
|
|
|
|
|
information for all entries in a given amateur band. |
|
11
|
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=head1 ACKNOWLEDGEMENTS |
|
13
|
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
Much inspiration was gained from povaX's ALLmon at |
|
15
|
|
|
|
|
|
|
https://github.com/poxaV/ALLmon/blob/master/ALLmon |
|
16
|
|
|
|
|
|
|
Thank you, povaX! |
|
17
|
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=head1 VERSION |
|
19
|
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
Version 0.01 |
|
21
|
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
23
|
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
Extract all log entries for a given band: |
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
use Ham::WSJTX::Logparse; |
|
27
|
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
my $log = Ham::WSJTX::Logparse->new(); |
|
29
|
|
|
|
|
|
|
# Looks in the default location for the ALL.TXT file |
|
30
|
|
|
|
|
|
|
# or... |
|
31
|
|
|
|
|
|
|
my $log = Ham::WSJTX::Logparse->new("/path/to/an/ALL.TXT"); |
|
32
|
|
|
|
|
|
|
# or, can parse multiple logs... |
|
33
|
|
|
|
|
|
|
my $log = Ham::WSJTX::Logparse->new("/tmp/ALL.TXT.one", "/tmp/ALL.TXT.two"); # etc., etc.... |
|
34
|
|
|
|
|
|
|
... |
|
35
|
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
# Define a callback |
|
37
|
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
my $callback = sub { |
|
39
|
|
|
|
|
|
|
my $date = shift; |
|
40
|
|
|
|
|
|
|
my $time = shift; |
|
41
|
|
|
|
|
|
|
my $power = shift; |
|
42
|
|
|
|
|
|
|
my $offset = shift; |
|
43
|
|
|
|
|
|
|
my $mode = shift; |
|
44
|
|
|
|
|
|
|
my $callsign = shift; |
|
45
|
|
|
|
|
|
|
my $grid = shift; |
|
46
|
|
|
|
|
|
|
print "date $date time $time power $power offset $offset mode $mode callsign $callsign grid $grid\n"; |
|
47
|
|
|
|
|
|
|
# sure you can do something interesting with this! |
|
48
|
|
|
|
|
|
|
}; |
|
49
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
$log->parseForBand("20m", $callback); |
|
51
|
|
|
|
|
|
|
# many entries are printed.... |
|
52
|
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
=head1 EXPORT |
|
54
|
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
No functions exported; this has a purely object-oriented module. |
|
56
|
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
=head1 SUBROUTINES/METHODS |
|
58
|
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
=head2 new(optional list of files) |
|
60
|
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
Constructs a new parser, given an optional list of files. If no files are given, the default locations will be checked |
|
62
|
|
|
|
|
|
|
for a WSJT-X ALL.TXT file. Returns a blessed hash. |
|
63
|
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
Note that this module has only been tested on OSX, and the location of default files on non-OSX/Windows platforms is not |
|
65
|
|
|
|
|
|
|
known to the author at this time; if you know, please inform me as new will die if it tries to load a default file on |
|
66
|
|
|
|
|
|
|
a platform I haven't coded for. |
|
67
|
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
=head2 files($self) |
|
69
|
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
Returns the list of files that the parser was configured with, or the default file as discovered if no files were |
|
71
|
|
|
|
|
|
|
supplied in the constructor. |
|
72
|
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
=head2 parseForBand($self, $bandOfInterest, $callback) |
|
74
|
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
Parses all discovered or supplied files, correctly determining the date of each 'heard station' entry, and if the entry |
|
76
|
|
|
|
|
|
|
relates to the band of interest, calls the callback with the entry details. |
|
77
|
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
The 'band of interest' is of the form nnnm, e.g. 20m, 2m, 160m, 2200m. Only one band can be filtered at any time. |
|
79
|
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
The callback is a sub as shown above in the synopsis. |
|
81
|
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
Take care with the 'grid' data in your callback: This is extracted from the logged content of a message, and must be |
|
83
|
|
|
|
|
|
|
two characers followed by two digits - but if the message was 'M0CUV SV2XYZ RR73', then the grid would be decoded as |
|
84
|
|
|
|
|
|
|
'RR73', which is not a valid grid square (of course, this is 'RR 73', but has been concatenated by the SV2 station). |
|
85
|
|
|
|
|
|
|
Similarly with TU73. In my callback, I use: |
|
86
|
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
if ($grid =~ /(TU|RR)73/) { |
|
88
|
|
|
|
|
|
|
warn "dodgy data from $date $time $callsign\n"; |
|
89
|
|
|
|
|
|
|
return; |
|
90
|
|
|
|
|
|
|
} |
|
91
|
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
Better validation may be considered for a later release. |
|
93
|
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
An entry is considered a 'heard station' entry if it has some text (maybe CQ or a callsign), followed by some text |
|
95
|
|
|
|
|
|
|
(most likely a callsign), followed by a grid square (two characters, two digits - see the note of caution in the |
|
96
|
|
|
|
|
|
|
previous paragragh). |
|
97
|
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
=head1 AUTHOR |
|
99
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
Matt Gumbley, C<< devzendo at cpan.org> >> |
|
101
|
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
=head1 BUGS |
|
103
|
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
Please report any bugs or feature requests to C, or through |
|
105
|
|
|
|
|
|
|
the web interface at L. I will be notified, and then you'll |
|
106
|
|
|
|
|
|
|
automatically be notified of progress on your bug as I make changes. |
|
107
|
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
=head1 SUPPORT |
|
109
|
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
|
111
|
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
perldoc Ham::WSJTX::Logparse |
|
113
|
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
You can also look for information at: |
|
116
|
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
=over 4 |
|
118
|
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker (report bugs here) |
|
120
|
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
L |
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
|
124
|
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
L |
|
126
|
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
=item * CPAN Ratings |
|
128
|
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
L |
|
130
|
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
=item * Search CPAN |
|
132
|
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
L |
|
134
|
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
=back |
|
136
|
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
|
139
|
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
Copyright 2016 Matt Gumbley. |
|
141
|
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License"); |
|
143
|
|
|
|
|
|
|
you may not use this file except in compliance with the License. |
|
144
|
|
|
|
|
|
|
You may obtain a copy of the License at |
|
145
|
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
L |
|
147
|
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software |
|
149
|
|
|
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS, |
|
150
|
|
|
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
151
|
|
|
|
|
|
|
See the License for the specific language governing permissions and |
|
152
|
|
|
|
|
|
|
limitations under the License. |
|
153
|
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
=cut |
|
156
|
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
our $VERSION = '0.01'; |
|
158
|
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
our %freqToBand = ( |
|
160
|
|
|
|
|
|
|
'144.491' => '2m', # +2 |
|
161
|
|
|
|
|
|
|
'144.489' => '2m', |
|
162
|
|
|
|
|
|
|
'70.093' => '4m', # +2 |
|
163
|
|
|
|
|
|
|
'70.091' => '4m', |
|
164
|
|
|
|
|
|
|
'50.278' => '6m', # +2 |
|
165
|
|
|
|
|
|
|
'50.276' => '6m', |
|
166
|
|
|
|
|
|
|
'28.078' => '10m', # +2 |
|
167
|
|
|
|
|
|
|
'28.076' => '10m', |
|
168
|
|
|
|
|
|
|
'24.919' => '12m', # +2 |
|
169
|
|
|
|
|
|
|
'24.917' => '12m', |
|
170
|
|
|
|
|
|
|
'21.078' => '15m', # +2 |
|
171
|
|
|
|
|
|
|
'21.076' => '15m', |
|
172
|
|
|
|
|
|
|
'18.104' => '17m', # +2 |
|
173
|
|
|
|
|
|
|
'18.102' => '17m', |
|
174
|
|
|
|
|
|
|
'14.078' => '20m', # +2 |
|
175
|
|
|
|
|
|
|
'14.076' => '20m', |
|
176
|
|
|
|
|
|
|
'10.14' => '30m', # +2 |
|
177
|
|
|
|
|
|
|
'10.138' => '30m', |
|
178
|
|
|
|
|
|
|
'7.078' => '40m', # +2 |
|
179
|
|
|
|
|
|
|
'7.076' => '40m', |
|
180
|
|
|
|
|
|
|
'5.359' => '60m', # +2 |
|
181
|
|
|
|
|
|
|
'5.357' => '60m', |
|
182
|
|
|
|
|
|
|
'3.578' => '80m', # +2 |
|
183
|
|
|
|
|
|
|
'3.576' => '80m', |
|
184
|
|
|
|
|
|
|
'1.84' => '160m', # +2 |
|
185
|
|
|
|
|
|
|
'1.838' => '160m', |
|
186
|
|
|
|
|
|
|
'0.4762' => '630m', # +2 |
|
187
|
|
|
|
|
|
|
'0.4742' => '630m', |
|
188
|
|
|
|
|
|
|
'0.13813' => '2200m', # +2 |
|
189
|
|
|
|
|
|
|
'0.13613' => '2200m', |
|
190
|
|
|
|
|
|
|
); |
|
191
|
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
sub new { |
|
193
|
0
|
|
|
0
|
1
|
|
my $class = shift; |
|
194
|
0
|
|
|
|
|
|
my @files = @_; |
|
195
|
|
|
|
|
|
|
|
|
196
|
0
|
0
|
|
|
|
|
my @filesToUse = scalar(@files) == 0 ? (defaultAllTxtFile()) : @files; |
|
197
|
0
|
|
|
|
|
|
my $obj = { |
|
198
|
|
|
|
|
|
|
files => [ @filesToUse ], |
|
199
|
|
|
|
|
|
|
}; |
|
200
|
|
|
|
|
|
|
|
|
201
|
0
|
|
|
|
|
|
foreach my $file (@{$obj->{files}}) { |
|
|
0
|
|
|
|
|
|
|
|
202
|
0
|
0
|
|
|
|
|
die "File '" . $file . "' not found" unless (-f $file); |
|
203
|
|
|
|
|
|
|
} |
|
204
|
0
|
|
|
|
|
|
bless $obj, $class; |
|
205
|
0
|
|
|
|
|
|
return $obj; |
|
206
|
|
|
|
|
|
|
} |
|
207
|
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
sub defaultAllTxtFile { |
|
209
|
0
|
0
|
0
|
0
|
0
|
|
if ($^O eq 'darwin') { |
|
|
|
0
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
# povaX' ALLmon suggests >= v1.4 uses standard OSX location: |
|
211
|
0
|
|
|
|
|
|
my $homeAll = "$ENV{HOME}/Library/Application Support/WSJT-X/ALL.TXT"; |
|
212
|
0
|
|
|
|
|
|
my $globalAll = "/Applications/WSJT-X/ALL.TXT"; # povaX' ALLmon suggests it was here in WSJT-X <= v1.3 |
|
213
|
0
|
0
|
|
|
|
|
return $homeAll if (-f $homeAll); |
|
214
|
0
|
0
|
|
|
|
|
return $globalAll if (-f $globalAll); |
|
215
|
0
|
|
|
|
|
|
die "Could not find default ALL.TXT"; |
|
216
|
|
|
|
|
|
|
} elsif ($^O =~ /^MSWin/ or $^O eq 'cygwin') { |
|
217
|
0
|
|
|
|
|
|
die "I don't have a Windows system to find the default location of ALL.TXT"; |
|
218
|
|
|
|
|
|
|
} else { |
|
219
|
|
|
|
|
|
|
# It has to be some sane kind of UNIX-like, right? |
|
220
|
0
|
|
|
|
|
|
die "I haven't tried this on non-OSX UNIX-likes"; |
|
221
|
|
|
|
|
|
|
} |
|
222
|
|
|
|
|
|
|
} |
|
223
|
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
sub files { |
|
225
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
|
226
|
0
|
|
|
|
|
|
return (@{$self->{files}}); |
|
|
0
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
} |
|
228
|
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
sub parseForBand { |
|
230
|
0
|
|
|
0
|
1
|
|
my ($self, $bandOfInterest, $callback) = @_; |
|
231
|
0
|
|
|
|
|
|
foreach my $file ($self->files()) { |
|
232
|
0
|
|
|
|
|
|
local *F; |
|
233
|
0
|
0
|
|
|
|
|
unless (open F, "<$file") { |
|
234
|
0
|
|
|
|
|
|
die "Cannot open $file: $!\n"; |
|
235
|
|
|
|
|
|
|
} |
|
236
|
0
|
|
|
|
|
|
my $currentBand = undef; |
|
237
|
0
|
|
|
|
|
|
my $currentDate = undef; |
|
238
|
0
|
|
|
|
|
|
while () { |
|
239
|
0
|
|
|
|
|
|
chomp; |
|
240
|
|
|
|
|
|
|
#print "line [$_]\n"; |
|
241
|
|
|
|
|
|
|
# Only interested in data from a specific band, and the indicator for changing band/mode looks like: |
|
242
|
|
|
|
|
|
|
# 2015-Apr-15 20:13 14.076 MHz JT9 |
|
243
|
|
|
|
|
|
|
# So extract the frequency, and look up the band. This also gives us the date. Records like this are always |
|
244
|
|
|
|
|
|
|
# written at startup, mode change, and at midnight. |
|
245
|
0
|
0
|
|
|
|
|
if (/^(\d{4}-\S{3}-\d{2}) \d{2}:\d{2}\s+(\d+\.\d+) MHz\s+\S+\s*$/) { |
|
246
|
0
|
|
|
|
|
|
$currentDate = $1; |
|
247
|
0
|
|
|
|
|
|
my $frequency = $2; |
|
248
|
0
|
|
|
|
|
|
$currentBand = $freqToBand{$frequency}; |
|
249
|
|
|
|
|
|
|
#print "data being received for $currentBand (filtering on $bandOfInterest)\n"; |
|
250
|
0
|
|
|
|
|
|
next; |
|
251
|
|
|
|
|
|
|
} |
|
252
|
|
|
|
|
|
|
# Time/Power/Freq offset/Mode/Call/Square can be extracted from records like these: |
|
253
|
|
|
|
|
|
|
# 0000 -9 1.5 1259 # CQ TI4DJ EK70 |
|
254
|
|
|
|
|
|
|
# 0001 -1 0.5 404 # DX K1RI FN41 |
|
255
|
|
|
|
|
|
|
# 0001 -8 0.2 560 # KC0EFQ WA3ETR FN10 |
|
256
|
|
|
|
|
|
|
# 0001 -15 0.1 628 # KK7X K8MDA EN80 |
|
257
|
|
|
|
|
|
|
# 0002 -13 1.1 1322 # CQ YV5FRD FK60 |
|
258
|
|
|
|
|
|
|
# 0003 -3 0.5 1002 # TF2MSN K1RI FN41 |
|
259
|
0
|
0
|
|
|
|
|
if (/^(\d{4})\s+(-\d+)\s+[-\d.]+\s+(\d+)\s([#@])\s\w+\s+(\w+)\s+([A-Z]{2}\d{2})\s*$/) { |
|
260
|
0
|
|
|
|
|
|
my $ctime = $1; |
|
261
|
0
|
|
|
|
|
|
my $cpower = $2; |
|
262
|
0
|
|
|
|
|
|
my $coffset = $3; |
|
263
|
0
|
|
|
|
|
|
my $cmode = $4; |
|
264
|
0
|
|
|
|
|
|
my $ccallsign = $5; |
|
265
|
0
|
|
|
|
|
|
my $cgrid = $6; |
|
266
|
|
|
|
|
|
|
# callsigns must have at least one digit. |
|
267
|
0
|
0
|
|
|
|
|
next unless ($ccallsign =~ /\d/); |
|
268
|
0
|
0
|
0
|
|
|
|
if (defined $currentDate && $bandOfInterest eq $currentBand) { |
|
269
|
0
|
|
|
|
|
|
$callback->($currentDate, $ctime, $cpower, $coffset, $cmode, $ccallsign, $cgrid); |
|
270
|
|
|
|
|
|
|
} |
|
271
|
0
|
|
|
|
|
|
next; |
|
272
|
|
|
|
|
|
|
} |
|
273
|
|
|
|
|
|
|
} |
|
274
|
0
|
|
|
|
|
|
close F; |
|
275
|
|
|
|
|
|
|
} |
|
276
|
|
|
|
|
|
|
} |
|
277
|
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
1; # End of Ham::WSJTX::Logparse |