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 |