~ubuntu-branches/ubuntu/raring/mtkbabel/raring

« back to all changes in this revision

Viewing changes to mtkbabel

  • Committer: Bazaar Package Importer
  • Author(s): Uwe Hermann
  • Date: 2008-02-08 21:52:33 UTC
  • Revision ID: james.westby@ubuntu.com-20080208215233-5hto58no3w1vx7uk
Tags: upstream-0.6
ImportĀ upstreamĀ versionĀ 0.6

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/perl
 
2
#
 
3
# Copyright (C) 2007 Niccolo Rigacci
 
4
#
 
5
# This program is free software; you can redistribute it and/or
 
6
# modify it under the terms of the GNU General Public License
 
7
# as published by the Free Software Foundation; either version 2
 
8
# of the License, or (at your option) any later version.
 
9
#
 
10
# This program is distributed in the hope that it will be useful,
 
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
# GNU General Public License for more details.
 
14
#
 
15
# You should have received a copy of the GNU General Public License
 
16
# along with this program; if not, write to the Free Software
 
17
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
18
#
 
19
# Author:       Niccolo Rigacci <niccolo@rigacci.org>
 
20
#
 
21
# Version:      0.6     2008-02-07
 
22
#
 
23
# Control program for GPS units using the MediaTek (MTK) chipset.
 
24
# Tested to work with i-Blue 747 GPS data logger.
 
25
#
 
26
 
 
27
use strict;
 
28
# Use the basename() function.
 
29
use File::Basename;
 
30
# Use the getopts() function.
 
31
use Getopt::Std;
 
32
use vars qw($opt_a $opt_b $opt_d $opt_E $opt_f $opt_h $opt_l $opt_m $opt_o $opt_p $opt_r $opt_t $opt_v $opt_w);
 
33
# Install the libdevice-serialport-perl Debian package.
 
34
use Device::SerialPort;
 
35
# Install the libtimedate-perl Debian package.
 
36
use Date::Format;
 
37
 
 
38
my $NAME = basename($0);
 
39
 
 
40
# Debug levels.
 
41
my $LOG_EMERG   = 0;
 
42
my $LOG_ALERT   = 1;
 
43
my $LOG_CRIT    = 2;
 
44
my $LOG_ERR     = 3;
 
45
my $LOG_WARNING = 4;
 
46
my $LOG_NOTICE  = 5;
 
47
my $LOG_INFO    = 6;
 
48
my $LOG_DEBUG   = 7;
 
49
 
 
50
# Size in bytes of data types.
 
51
my $SIZEOF_BYTE   = 1;
 
52
my $SIZEOF_WORD   = 2;
 
53
my $SIZEOF_LONG   = 4;
 
54
my $SIZEOF_FLOAT  = 4;
 
55
my $SIZEOF_DOUBLE = 8;
 
56
 
 
57
# Log format is stored as a bitmask field.
 
58
my $LOG_FORMAT_UTC         = 0x00000001;
 
59
my $LOG_FORMAT_VALID       = 0x00000002;
 
60
my $LOG_FORMAT_LATITUDE    = 0x00000004;
 
61
my $LOG_FORMAT_LONGITUDE   = 0x00000008;
 
62
my $LOG_FORMAT_HEIGHT      = 0x00000010;
 
63
my $LOG_FORMAT_SPEED       = 0x00000020;
 
64
my $LOG_FORMAT_HEADING     = 0x00000040;
 
65
my $LOG_FORMAT_DSTA        = 0x00000080;
 
66
my $LOG_FORMAT_DAGE        = 0x00000100;
 
67
my $LOG_FORMAT_PDOP        = 0x00000200;
 
68
my $LOG_FORMAT_HDOP        = 0x00000400;
 
69
my $LOG_FORMAT_VDOP        = 0x00000800;
 
70
my $LOG_FORMAT_NSAT        = 0x00001000;
 
71
my $LOG_FORMAT_SID         = 0x00002000;
 
72
my $LOG_FORMAT_ELEVATION   = 0x00004000;
 
73
my $LOG_FORMAT_AZIMUTH     = 0x00008000;
 
74
my $LOG_FORMAT_SNR         = 0x00010000;
 
75
my $LOG_FORMAT_RCR         = 0x00020000;
 
76
my $LOG_FORMAT_MILLISECOND = 0x00040000;
 
77
my $LOG_FORMAT_DISTANCE    = 0x00080000;
 
78
 
 
79
# Log status is stored as a bitmask field;
 
80
my $LOG_STATUS_AUTOLOG        = 0x0002; # AUTO_LOG mode (by criteria) ON/OFF
 
81
my $LOG_STATUS_STOP_WHEN_FULL = 0x0004; # STOP/OVERLAP method when memory full 
 
82
my $LOG_STATUS_ENABLE         = 0x0100; # Device is in ENABLE_LOG (normal) state
 
83
my $LOG_STATUS_DISABLE        = 0x0200; # Device entered DISABLE_LOG state (fail sectors >= 16)
 
84
my $LOG_STATUS_NEED_FORMAT    = 0x0400; # Flash memory need format
 
85
my $LOG_STATUS_FULL           = 0x0800; # Flash memory is full
 
86
 
 
87
my $SIZEOF_LOG_UTC         = $SIZEOF_LONG;
 
88
my $SIZEOF_LOG_VALID       = $SIZEOF_WORD;
 
89
my $SIZEOF_LOG_LATITUDE    = $SIZEOF_DOUBLE;
 
90
my $SIZEOF_LOG_LONGITUDE   = $SIZEOF_DOUBLE;
 
91
my $SIZEOF_LOG_HEIGHT      = $SIZEOF_FLOAT;
 
92
my $SIZEOF_LOG_SPEED       = $SIZEOF_FLOAT;
 
93
my $SIZEOF_LOG_HEADING     = $SIZEOF_FLOAT;
 
94
my $SIZEOF_LOG_DSTA        = $SIZEOF_WORD;
 
95
my $SIZEOF_LOG_DAGE        = $SIZEOF_LONG;
 
96
my $SIZEOF_LOG_PDOP        = $SIZEOF_WORD;
 
97
my $SIZEOF_LOG_HDOP        = $SIZEOF_WORD;
 
98
my $SIZEOF_LOG_VDOP        = $SIZEOF_WORD;
 
99
my $SIZEOF_LOG_NSAT        = $SIZEOF_BYTE * 2;
 
100
my $SIZEOF_LOG_SID         = $SIZEOF_BYTE;
 
101
my $SIZEOF_LOG_SIDINUSE    = $SIZEOF_BYTE;
 
102
my $SIZEOF_LOG_SATSINVIEW  = $SIZEOF_WORD;
 
103
my $SIZEOF_LOG_ELEVATION   = $SIZEOF_WORD;
 
104
my $SIZEOF_LOG_AZIMUTH     = $SIZEOF_WORD;
 
105
my $SIZEOF_LOG_SNR         = $SIZEOF_WORD;
 
106
my $SIZEOF_LOG_RCR         = $SIZEOF_WORD;
 
107
my $SIZEOF_LOG_MILLISECOND = $SIZEOF_WORD;
 
108
my $SIZEOF_LOG_DISTANCE    = $SIZEOF_DOUBLE;
 
109
 
 
110
# A record separator has one of the following types.
 
111
my $SEP_TYPE_CHANGE_LOG_BITMASK    = 0x02;
 
112
my $SEP_TYPE_CHANGE_LOG_PERIOD     = 0x03;
 
113
my $SEP_TYPE_CHANGE_LOG_DISTANCE   = 0x04;
 
114
my $SEP_TYPE_CHANGE_LOG_SPEED      = 0x05;
 
115
my $SEP_TYPE_CHANGE_OVERLAP_STOP   = 0x06;
 
116
my $SEP_TYPE_CHANGE_START_STOP_LOG = 0x07;
 
117
 
 
118
# Values for the VALID field.
 
119
my $VALID_NOFIX     = 0x0001;
 
120
my $VALID_SPS       = 0x0002;
 
121
my $VALID_DGPS      = 0x0004;
 
122
my $VALID_PPS       = 0x0008;
 
123
my $VALID_RTK       = 0x0010;
 
124
my $VALID_FRTK      = 0x0020;
 
125
my $VALID_ESTIMATED = 0x0040;
 
126
my $VALID_MANUAL    = 0x0080;
 
127
my $VALID_SIMULATOR = 0x0100;
 
128
 
 
129
# Values for the RCR field.
 
130
my $RCR_TIME     = 0x01;
 
131
my $RCR_SPEED    = 0x02;
 
132
my $RCR_DISTANCE = 0x04;
 
133
my $RCR_BUTTON   = 0x08;
 
134
 
 
135
# Recording method: OVERLAP or STOP.
 
136
my $RCD_METHOD_OVP = 1;
 
137
my $RCD_METHOD_STP = 2;
 
138
 
 
139
# Log data is retrieved in chunks of this size.
 
140
my $SIZEOF_CHUNK         = 0x800;
 
141
my $SIZEOF_SECTOR        = 0x10000;
 
142
my $SIZEOF_SECTOR_HEADER = 0x200;
 
143
my $SIZEOF_SEPARATOR     = 0x10;
 
144
   
 
145
# End Of Line for generate GPX files.
 
146
my $GPX_EOL = "\n";
 
147
 
 
148
# Default timeout for packet wait (sec).
 
149
my $TIMEOUT = 5;
 
150
# Timeout for activity on device port (msec).
 
151
my $TIMEOUT_IDLE_PORT = 5000;
 
152
 
 
153
#-------------------------------------------------------------------------
 
154
# Global variablee.
 
155
#-------------------------------------------------------------------------
 
156
my $debug = $LOG_ERR;         # Default loggin level.
 
157
my $port  = '/dev/ttyUSB0';   # Default communication port.
 
158
 
 
159
# GPX global values.
 
160
my $gpx_trk_minlat =   90.0;
 
161
my $gpx_trk_minlon =  180.0;
 
162
my $gpx_trk_maxlat =  -90.0;
 
163
my $gpx_trk_maxlon = -180.0;
 
164
my $gpx_wpt_minlat =   90.0;
 
165
my $gpx_wpt_minlon =  180.0;
 
166
my $gpx_wpt_maxlat =  -90.0;
 
167
my $gpx_wpt_maxlon = -180.0;
 
168
my $gpx_trk_number = 0;
 
169
my $gpx_wpt_number = 0;
 
170
 
 
171
my $device;
 
172
my $version;
 
173
my $release;
 
174
my $model_id;
 
175
my $ret;
 
176
my $log_format;
 
177
my $next_write_address;
 
178
my $expected_records_total;
 
179
my $log_status;
 
180
my $rec_method;
 
181
my $fail_sectors;
 
182
my $sectors;
 
183
my $bytes_to_read;
 
184
my $fp;
 
185
my $fp_log;
 
186
my $offset;
 
187
my $non_written_sector_found;
 
188
 
 
189
# Record values.
 
190
my $record_utc;
 
191
my $record_valid;
 
192
my $record_latitude;
 
193
my $record_longitude;
 
194
my $record_height;
 
195
my $record_speed;
 
196
my $record_heading;
 
197
my $record_dsta;
 
198
my $record_dage;
 
199
my $record_pdop;
 
200
my $record_hdop;
 
201
my $record_vdop;
 
202
my $record_nsat_in_use;
 
203
my $record_nsat_in_view;
 
204
my $record_satdata;
 
205
my $record_rcr;
 
206
my $record_millisecond;
 
207
my $record_distance;
 
208
 
 
209
#-------------------------------------------------------------------------
 
210
# Get options from command line.
 
211
#-------------------------------------------------------------------------
 
212
if (! getopts('ab:d:Ef:hl:m:o:p:r:tvw') or $opt_h) {
 
213
    my $str1 = describe_log_format(0x00fff);
 
214
    my $str2 = describe_log_format(0xff000);
 
215
    print <<HELP;
 
216
Usage: $NAME [options]
 
217
Options:
 
218
    -p port                  Communication port, default: $port
 
219
    -f filename              Base name for saved files (.bin and .gpx)
 
220
    -w                       Create a gpx file with waypoints
 
221
    -t                       Create a gpx file with tracks
 
222
    -a                       Read all the log memory (overlapped data)
 
223
    -E                       Erase data log memory
 
224
    -l {on|off}              Turn loggin ON/OFF
 
225
    -m {stop|overlap}        Set STOP/OVERLAP recording method on memory full
 
226
    -r time:distance:speed   Set logging criteria (zero to disable):
 
227
                             every 1-999 seconds, every 10-9999 meters, over 10-999 km/h
 
228
    -o log_format            Enable or disable log fields (FIELD1,-FIELD2,...), available fields:
 
229
                             $str1
 
230
                             $str2
 
231
    -b filename.bin          Do not read device, read a previously saved .bin file
 
232
    -d debug_level           Debug level: 0..7
 
233
    -v                       Print MTKBabel version and exit
 
234
HELP
 
235
    exit(1)
 
236
}
 
237
 
 
238
#-------------------------------------------------------------------------
 
239
# Check command line options.
 
240
#-------------------------------------------------------------------------
 
241
if ($opt_v) { print "\nMTKBabel Version 0.6\n\n"; exit }
 
242
$debug = $opt_d if (defined($opt_d) and ($opt_d >= $LOG_EMERG) and ($opt_d <= $LOG_DEBUG));
 
243
$port  = $opt_p if (defined($opt_p));
 
244
$opt_f = substr($opt_f, 0, -4) if (substr($opt_f, -4) eq '.bin');
 
245
$opt_b = substr($opt_b, 0, -4) if (substr($opt_b, -4) eq '.bin');
 
246
 
 
247
#-------------------------------------------------------------------------
 
248
# Do not open the device, read instead an existing binary log file.
 
249
#-------------------------------------------------------------------------
 
250
if ($opt_b) {
 
251
    if ($opt_t or $opt_w) {
 
252
        # Parse binary data and save GPX files.
 
253
        $opt_f = $opt_b;
 
254
        # Total number of records is unknown: we will exit on End Of File.
 
255
        $expected_records_total = 0xffffffff;
 
256
        parse_log_data();
 
257
    }
 
258
    exit;
 
259
}
 
260
 
 
261
#-------------------------------------------------------------------------
 
262
# Initialize the device port.
 
263
#-------------------------------------------------------------------------
 
264
serial_port_open($port);
 
265
 
 
266
# Do not write the log file yet.
 
267
undef($fp_log);
 
268
 
 
269
# Send test packet (PMTK_TEST).
 
270
packet_send('PMTK000');
 
271
packet_wait('PMTK001,0,');
 
272
print "MTK Test OK\n";
 
273
 
 
274
# Query firmware version (PMTK_Q_VERSION).
 
275
packet_send('PMTK604');
 
276
$ret = packet_wait('PMTK001,604,');
 
277
if ($ret =~ m/PMTK001,604,([0-9A-Za-z]+)\*/) {
 
278
    $version = $1;
 
279
}
 
280
 
 
281
# Query firmware release (PMTK_Q_RELEASE).
 
282
packet_send('PMTK605');
 
283
$ret = packet_wait('PMTK705,');
 
284
if ($ret =~ m/PMTK705,([\.0-9A-Za-z_-]+),([0-9A-Za-z]+)\*/) {
 
285
    $release  = $1;
 
286
    $model_id = $2;
 
287
}
 
288
 
 
289
printf "MTK Firmware: Version: $version, Release: $release, Model ID: $model_id\n";
 
290
 
 
291
#-------------------------------------------------------------------------
 
292
# Erase memory.
 
293
#-------------------------------------------------------------------------
 
294
if ($opt_E) {
 
295
    printf(">> Erasing log memory...\n");
 
296
    packet_send('PMTK182,6,1');
 
297
    packet_wait('PMTK001,182,6,3', 20);
 
298
}
 
299
 
 
300
#-------------------------------------------------------------------------
 
301
# Turn ON or OFF data logging.
 
302
#-------------------------------------------------------------------------
 
303
if ($opt_l eq 'on') {
 
304
    printf(">> Switch recording to ON\n");
 
305
    # Send PMTK_LOG_ON.
 
306
    packet_send('PMTK182,4');
 
307
    packet_wait('PMTK001,182,4,3');
 
308
}
 
309
if ($opt_l eq 'off') {
 
310
    printf(">> Switch recording to OFF\n");
 
311
    # Send PMTK_LOG_OFF.
 
312
    packet_send('PMTK182,5');
 
313
    packet_wait('PMTK001,182,5,3');
 
314
}
 
315
 
 
316
#-------------------------------------------------------------------------
 
317
# Set recording criteria: TIME, DISTANCE, SPEED.
 
318
#-------------------------------------------------------------------------
 
319
if ($opt_r) {
 
320
    printf(">> Setting recording criteria: time, distance, speed\n");
 
321
    my ($time, $distance, $speed) = split(/:/, $opt_r);
 
322
    undef($time)     if ($time     eq '');
 
323
    undef($distance) if ($distance eq '');
 
324
    undef($speed)    if ($speed    eq '');
 
325
    $time     = int($time    ) if (defined($time));
 
326
    $distance = int($distance) if (defined($distance));
 
327
    $speed    = int($speed   ) if (defined($speed));
 
328
    if (defined($time) and (($time >= 1 and $time <= 999) or ($time == 0))) {
 
329
        packet_send(sprintf('PMTK182,1,3,%u', $time * 10));
 
330
        packet_wait('PMTK001,182,1,3');
 
331
    }
 
332
    if (defined($distance) and (($distance >= 10 and $distance <= 9999) or ($distance == 0))) {
 
333
        packet_send(sprintf('PMTK182,1,4,%u', $distance * 10));
 
334
        packet_wait('PMTK001,182,1,3');
 
335
    }
 
336
    if (defined($speed) and (($speed >= 10 and $speed <= 999) or ($speed == 0))) {
 
337
        packet_send(sprintf('PMTK182,1,5,%u', $speed * 10));
 
338
        packet_wait('PMTK001,182,1,3');
 
339
    }
 
340
}
 
341
 
 
342
#-------------------------------------------------------------------------
 
343
# Set recording method: OVERLAP or STOP (PMTK_LOG_REC_METHOD).
 
344
#-------------------------------------------------------------------------
 
345
if ((lc($opt_m) eq 'overlap') or (lc($opt_m) eq 'stop')) {
 
346
    if (lc($opt_m) eq 'overlap') {
 
347
        printf(">> Setting method OVERLAP on memory full\n");
 
348
        packet_send('PMTK182,1,6,1');
 
349
    } else {
 
350
        printf(">> Setting method STOP on memory full\n");
 
351
        packet_send('PMTK182,1,6,2');
 
352
    }
 
353
    $ret = packet_wait('PMTK001,182,1,');
 
354
    if ($ret =~ m/PMTK001,182,1,(\d)/) {
 
355
        if ($1 ne '3') {
 
356
            printf(">> ERROR: Cannot set recording method\n");
 
357
        }
 
358
    }
 
359
}
 
360
 
 
361
#-------------------------------------------------------------------------
 
362
# Set log format (PMTK_LOG_SETFORMAT).
 
363
#-------------------------------------------------------------------------
 
364
if ($opt_o) {
 
365
    printf(">> Setting log format\n");
 
366
    # Get current log format.
 
367
    packet_send('PMTK182,2,2');
 
368
    $ret = packet_wait('PMTK182,3,2,');
 
369
    packet_wait('PMTK001,182,2,3');
 
370
    if ($ret =~ m/PMTK182,3,2,([0-9A-Za-z]+)\*/) {
 
371
        $log_format = hex($1);
 
372
        $log_format = encode_log_format($log_format, $opt_o);
 
373
        packet_send(sprintf('PMTK182,1,2,%08X', $log_format));
 
374
        $ret = packet_wait('PMTK001,182,1,3');
 
375
    }
 
376
}
 
377
 
 
378
 
 
379
# Query log format (PMTK_LOG_QUERY).
 
380
packet_send('PMTK182,2,2');
 
381
$ret = packet_wait('PMTK182,3,2,');
 
382
packet_wait('PMTK001,182,2,3');
 
383
if ($ret =~ m/PMTK182,3,2,([0-9A-Za-z]+)\*/) {
 
384
    $log_format = hex($1);
 
385
    my ($size_wpt, $size_sat) = sizeof_log_format($log_format);
 
386
    printf("Log format: (%s) %s\n", $1, describe_log_format($log_format));
 
387
    printf("Size in bytes of each log record: %u + (%u * sats_in_view)\n", $size_wpt + 2, $size_sat);
 
388
}
 
389
 
 
390
# Query recording criteria: time, distance, speed (PMTK_LOG_QUERY).
 
391
packet_send('PMTK182,2,3');
 
392
$ret = packet_wait('PMTK182,3,3,');
 
393
if ($ret =~ m/PMTK182,3,3,([0-9]+)\*/) {
 
394
    printf("Logging TIME interval:     %6.2f s\n", $1 / 10);
 
395
}
 
396
packet_send('PMTK182,2,4');
 
397
$ret = packet_wait('PMTK182,3,4,');
 
398
if ($ret =~ m/PMTK182,3,4,([0-9]+)\*/) {
 
399
    printf("Logging DISTANCE interval: %6.2f m\n", $1 / 10);
 
400
}
 
401
packet_send('PMTK182,2,5');
 
402
$ret = packet_wait('PMTK182,3,5,');
 
403
if ($ret =~ m/PMTK182,3,5,([0-9]+)\*/) {
 
404
    printf("Logging SPEED limit:       %6.2f km/h\n", $1 / 10);
 
405
}
 
406
 
 
407
# Query recording method when full (OVERLAP/STOP).
 
408
packet_send('PMTK182,2,6');
 
409
packet_wait('PMTK001,182,2,3');
 
410
$ret = packet_wait('PMTK182,3,6,');
 
411
if ($ret =~ m/PMTK182,3,6,([0-9]+)\*/) {
 
412
    $rec_method = $1;
 
413
    printf("Recording method on memory full: (%u) %s\n", $rec_method, describe_recording_method($rec_method));
 
414
}
 
415
 
 
416
# Query LOG_STATUS (recording ON/OFF, ...).
 
417
packet_send('PMTK182,2,7');
 
418
packet_wait('PMTK001,182,2,3');
 
419
$ret = packet_wait('PMTK182,3,7,');
 
420
if ($ret =~ m/PMTK182,3,7,([0-9A-Za-z]+)\*/) {
 
421
    $log_status = $1;
 
422
    printf("Log status: (%012b) %s\n", $log_status, describe_log_status($log_status));
 
423
    if ($log_status & $LOG_STATUS_NEED_FORMAT) {
 
424
        printf("WARNING! Log status NEED_FORMAT, log data is not valid!\n");
 
425
    }
 
426
    if ($log_status & $LOG_STATUS_DISABLE) {
 
427
        printf("WARNING! Log status DISABLE_LOG, may too many failed sectors!\n");
 
428
    }
 
429
}
 
430
 
 
431
# Query the RCD_ADDR (data log Next Write Address).
 
432
# If device is in STOP mode, this is also the total memory used.
 
433
# If it is in OVERLAP mode, there is old data beyond the NWA.
 
434
packet_send('PMTK182,2,8');
 
435
$ret = packet_wait('PMTK182,3,8,');
 
436
packet_wait('PMTK001,182,2,3');
 
437
if ($ret =~ m/PMTK182,3,8,([0-9A-Za-z]+)\*/) {
 
438
    $next_write_address = hex($1);
 
439
    printf("Next write address: %u (0x%08X)\n", $next_write_address, $next_write_address);
 
440
}
 
441
 
 
442
# Query number of records stored in the log.
 
443
packet_send('PMTK182,2,10');
 
444
$ret = packet_wait('PMTK182,3,10,');
 
445
packet_wait('PMTK001,182,2,3');
 
446
if ($ret =~ m/PMTK182,3,10,([0-9A-Za-z]+)\*/) {
 
447
    $expected_records_total = hex($1);
 
448
    printf("Number of records: %u\n", $expected_records_total);
 
449
}
 
450
 
 
451
# Query failed sectors (PMTK_RCD_FSECTOR).
 
452
packet_send('PMTK182,2,11');
 
453
$ret = packet_wait('PMTK182,3,11,');
 
454
packet_wait('PMTK001,182,2,3');
 
455
if ($ret =~ m/PMTK182,3,11,([0-9A-Za-z]+)\*/) {
 
456
    $fail_sectors = $1;
 
457
    printf("Memory health status (failed sectors mask): %s\n", $fail_sectors);
 
458
}
 
459
 
 
460
 
 
461
#-------------------------------------------------------------------------
 
462
# Get binary data from the device and save to a file.
 
463
#-------------------------------------------------------------------------
 
464
if ($opt_f and ($opt_t or $opt_w)) {
 
465
 
 
466
    # Compute the memory used by data log, round-up to the entire sector.
 
467
    if (($rec_method == $RCD_METHOD_OVP) or $opt_a) {
 
468
        # In OVERLAP mode we don't know where data ends; read the entire memory.
 
469
        $bytes_to_read = flash_memory_size($model_id);
 
470
    } else {
 
471
        # In STOP mode read from zero to Next Write Address.
 
472
        $sectors  = int($next_write_address / $SIZEOF_SECTOR);
 
473
        $sectors += 1 if (($next_write_address % $SIZEOF_SECTOR) != 0);
 
474
        $bytes_to_read = $sectors * $SIZEOF_SECTOR;
 
475
    }
 
476
 
 
477
    printf(">> Retrieving %u (0x%08X) bytes of log data from device...\n", $bytes_to_read, $bytes_to_read);
 
478
    open($fp_log, ">${opt_f}.bin") or die("Cannot open file ${opt_f}.bin: $!");
 
479
 
 
480
    # Avoid reading the entire memory if we find a non-written sector.
 
481
    $non_written_sector_found = 0;
 
482
 
 
483
    # NOTE: On a slow machine there was some problem getting the entire log data
 
484
    # via USB port with a single PMTK_LOG_REQ_DATA request.
 
485
    # The GPS device eventually begins to send packets longer than $SIZEOF_CHUNK,
 
486
    # apparently with corrupted data (failed checksum).
 
487
 
 
488
    # To be safe we iterate requesting $SIZEOF_CHUNK bytes at time.
 
489
    for ($offset = 0; $offset < $bytes_to_read; $offset += $SIZEOF_CHUNK) {
 
490
        # Request log data (PMTK_LOG_REQ_DATA) from $offset to $bytes_to_read.
 
491
        packet_send(sprintf('PMTK182,7,%08X,%08X', $offset, $SIZEOF_CHUNK));
 
492
        # Start writing binary data to file and wait the final PMTK_ACK packet.
 
493
        packet_wait('PMTK001,182,7,3', 10);
 
494
        last if ($non_written_sector_found);
 
495
    }
 
496
    close($fp_log);
 
497
    undef($fp_log);
 
498
 
 
499
    # Parse binary data and save GPX files.
 
500
    parse_log_data();
 
501
 
 
502
}
 
503
 
 
504
serial_port_close();
 
505
 
 
506
exit;
 
507
 
 
508
#-------------------------------------------------------------------------
 
509
# Calculate the packet checksum: bitwise XOR of string's bytes.
 
510
#-------------------------------------------------------------------------
 
511
sub packet_checksum {
 
512
 
 
513
    my $pkt   = shift;
 
514
    my $len   = length($pkt);
 
515
    my $check = 0;
 
516
    my $i;
 
517
 
 
518
    for ($i = 0; $i < $len; $i++) { $check ^= ord(substr($pkt, $i, 1)); }
 
519
    #printf("0x%02X\n", $check);
 
520
    return($check);
 
521
}
 
522
 
 
523
#-------------------------------------------------------------------------
 
524
# Send NMEA packet to the device.
 
525
#-------------------------------------------------------------------------
 
526
sub packet_send {
 
527
 
 
528
    my $pkt = shift;
 
529
    my $n;
 
530
 
 
531
    # Add the checksum to the packet.
 
532
    $pkt = $pkt . '*' . sprintf('%02X', packet_checksum($pkt));
 
533
    printf("%s TX packet => %s\n", log_time(), $pkt) if ($debug >= $LOG_NOTICE);
 
534
    # Add the preamble and <CR><LF>.
 
535
    $pkt = '$' . $pkt . "\r\n";
 
536
 
 
537
    $n = serial_port_write($pkt);
 
538
    printf("Writing %u bytes to device; actually written %u bytes\n", length($pkt), $n) if ($debug >= $LOG_DEBUG);
 
539
    die("ERROR: Writing to device: $!") if ($n != length($pkt));
 
540
}
 
541
 
 
542
#-------------------------------------------------------------------------
 
543
# Read a packet from the device.
 
544
# Return the packet with PktType, DataField, "*" and Checksum.
 
545
#
 
546
#   Example: PMTK182,3,8,0004E69C*13
 
547
#
 
548
# The packet received has a leading Preample and a trailing <CR><LF>,
 
549
# example: $PMTK182,3,8,0004E69C*13<CR><LF>
 
550
#-------------------------------------------------------------------------
 
551
sub packet_read {
 
552
 
 
553
    my $timeout = shift;
 
554
    my $c;
 
555
    my $n;
 
556
    my $pkt;
 
557
    my $previous_c;
 
558
    my $payload;
 
559
    my $checksum;
 
560
 
 
561
    # Timeout (in milliseconds) for activity on the port.
 
562
    $timeout = $TIMEOUT_IDLE_PORT if (!defined($timeout));
 
563
    serial_port_set_read_timeout($timeout);
 
564
 
 
565
    # Wait packet preamble.
 
566
    $c = '';
 
567
    while ($c ne '$') {
 
568
        ($n, $c) = serial_port_getch();
 
569
        die("ERROR: Reading from device: $!") if ($n != 1);
 
570
    }
 
571
 
 
572
    # Read until End Of Packet.
 
573
    $pkt = '';
 
574
    $previous_c = '';
 
575
    while (1) {
 
576
        ($n, $c) = serial_port_getch();
 
577
        die("ERROR: Reading from device: $!") if ($n != 1);
 
578
        if ($c eq '$') {
 
579
            $pkt = '';
 
580
        } else {
 
581
            $pkt .= $c;
 
582
        }
 
583
        if (($c eq "\n") and ($previous_c eq "\r")) {
 
584
            last;
 
585
        }
 
586
        $previous_c = $c;
 
587
    }
 
588
 
 
589
    # Remove trailing <CR><LF>.
 
590
    $pkt = substr($pkt, 0, -2);
 
591
    printf("%s RX packet <= %s\n", log_time(), $pkt) if ($debug >= $LOG_NOTICE);
 
592
 
 
593
    # Extract packet payload and checksum.
 
594
    $payload  = substr($pkt,  0, -3);
 
595
    $checksum = hex(substr($pkt, -2,  2));
 
596
 
 
597
    # Verify packet checksum.
 
598
    if ($checksum ne packet_checksum($payload)) {
 
599
        printf("Packet checksum error: expected 0x%02X, computed 0x%02X\n", $checksum, packet_checksum($payload)) if ($debug >= $LOG_ERR);
 
600
        return('');
 
601
    } else {
 
602
        return($pkt);
 
603
    }
 
604
}
 
605
 
 
606
#-------------------------------------------------------------------------
 
607
# Read packets from the device, untill we get the one we want.
 
608
#-------------------------------------------------------------------------
 
609
sub packet_wait {
 
610
 
 
611
    my $pkt_type = shift;
 
612
    my $timeout  = shift;
 
613
    my $max_time;
 
614
    my $pkt;
 
615
    my $len;
 
616
    my $i;
 
617
 
 
618
    $len = length($pkt_type);
 
619
 
 
620
    # Timeout (in seconds) for packet wait.
 
621
    $timeout = $TIMEOUT if (!defined($timeout));
 
622
    $max_time = time() + $timeout;
 
623
 
 
624
    while(1) {
 
625
        $pkt = packet_read($timeout * 1000);
 
626
        return($pkt) if (substr($pkt, 0, $len) eq $pkt_type);
 
627
        write_log_packet($pkt) if (defined($fp_log));
 
628
        last if (time() > $max_time);
 
629
    }
 
630
    printf("%s ERROR: packet_wait() failed for packet %s\n", log_time(), $pkt_type) if ($debug >= $LOG_ERR);
 
631
    return(undef);
 
632
}
 
633
 
 
634
#-------------------------------------------------------------------------
 
635
# Append received packets of log data to a file.
 
636
#
 
637
# Packet format is PMTK182,8,SSSSSSSS,PacketData*CC where:
 
638
#   SSSSSSSS   = Offset of first byte (hex value, 8 chars)
 
639
#   PacketData = Packet data (hex values, 4096 chars)
 
640
#   *          = Separator (1 char)
 
641
#   CC         = Checksum of data (hex value, 2 chars)
 
642
#-------------------------------------------------------------------------
 
643
sub write_log_packet {
 
644
 
 
645
    my $pkt = shift;
 
646
    my $pkt_len;
 
647
    my $pkt_offset;
 
648
    my $log_offset;
 
649
    my $percent;
 
650
    my $i;
 
651
 
 
652
    # Save only datalog packets (PMTK_LOG_RESP_DATA).
 
653
    return if (substr($pkt, 0, 10) ne 'PMTK182,8,');
 
654
 
 
655
    # Check if packet length is OK and if chunk is in sequence order.
 
656
    $log_offset = tell($fp_log);
 
657
    $pkt_offset = hex(substr($pkt, 10, 8));
 
658
    $pkt_len    = ($SIZEOF_CHUNK * 2) + 22;
 
659
 
 
660
    # Check sector headers for non-written pattern, set a flag to avoid reading unused memory.
 
661
    if (($log_offset % $SIZEOF_SECTOR) == 0) {
 
662
        if (uc(substr($pkt, 19, $SIZEOF_SEPARATOR * 2)) eq ('FF' x $SIZEOF_SEPARATOR)) {
 
663
            printf("WARNING: Sector header at offset 0x%08X is non-written data\n", $log_offset);
 
664
            $non_written_sector_found = 1;
 
665
        }
 
666
    }
 
667
 
 
668
    # We request $pkt_len at time, check how much we got.
 
669
    if (length($pkt) != $pkt_len) {
 
670
        printf("WARNING: Packet size error: expected %04X, got %04X\n", $pkt_len, length($pkt)) if ($debug >= $LOG_WARNING);
 
671
    }
 
672
 
 
673
    if ($pkt_offset != $log_offset) {
 
674
        printf("ERROR: Chunk out of sequence: expected %08X, got %08X\n", $log_offset, $pkt_offset) if ($debug >= $LOG_ERR);
 
675
    } else {
 
676
        printf("Saving log data, offset: 0x%08X\n", $log_offset) if ($debug >= $LOG_INFO);
 
677
        # Convert the string of hex values into binary data.
 
678
        for ($i = 19; $i < ($pkt_len - 3); $i += 2) {
 
679
            printf $fp_log chr(hex(substr($pkt, $i, 2)));
 
680
        }
 
681
        $percent = ($log_offset / $bytes_to_read) * 100;
 
682
        printf("Saved log data: %6.2f%%\n", $percent);
 
683
    }
 
684
}
 
685
 
 
686
#-------------------------------------------------------------------------
 
687
# Parse raw binary log data and write GPX files.
 
688
#-------------------------------------------------------------------------
 
689
sub parse_log_data {
 
690
 
 
691
    my $i;
 
692
    my $fp;
 
693
    my $fp_gpx;
 
694
    my $fp_gpx_trk;
 
695
    my $fp_gpx_wpt;
 
696
    my $buffer;
 
697
    my $log_len;
 
698
    my $new_offset;
 
699
    my $log_format;
 
700
    my $checksum;
 
701
 
 
702
    my $expected_records_sector = 0;
 
703
    my $record_count_sector     = 0;
 
704
    my $record_count_total      = 0;
 
705
    my $gpx_in_trk              = 0;
 
706
 
 
707
    my $gpx_trk_fname     = "${opt_f}_trk.gpx";
 
708
    my $gpx_wpt_fname     = "${opt_f}_wpt.gpx";
 
709
    my $gpx_trk_tmp_fname = "${opt_f}_trk.gpx.$$.tmp";
 
710
    my $gpx_wpt_tmp_fname = "${opt_f}_wpt.gpx.$$.tmp";
 
711
 
 
712
    # Open the binary log file for reading.
 
713
    open($fp, "${opt_f}.bin") or die("ERROR reading ${opt_f}.bin: $!");
 
714
    seek($fp, 0, 2) or die;
 
715
    $log_len = tell($fp);
 
716
    seek($fp, 0, 0) or die;
 
717
 
 
718
    # Open GPX temporary files for writing tracks and waypoints.
 
719
    open($fp_gpx_trk, ">$gpx_trk_tmp_fname") or die("ERROR writing $gpx_trk_tmp_fname: $!") if ($opt_t);
 
720
    open($fp_gpx_wpt, ">$gpx_wpt_tmp_fname") or die("ERROR writing $gpx_wpt_tmp_fname: $!") if ($opt_w);
 
721
 
 
722
    while (1) {
 
723
 
 
724
        # Print current file position.
 
725
        printf("Reading offset %08X\n", tell($fp)) if ($debug >= $LOG_INFO);
 
726
 
 
727
        #-----------------------------------------
 
728
        # Process the begin of a sector (header).
 
729
        #-----------------------------------------
 
730
        if ((tell($fp) % $SIZEOF_SECTOR) == 0) {
 
731
            # Reached the begin of a log sector (every 0x10000 bytes). Get the header (0x200 bytes).
 
732
            $buffer = my_read($fp, $SIZEOF_SECTOR_HEADER);
 
733
            # What we will find in this log sector:
 
734
            ($expected_records_sector, $log_format) = parse_sector_header($buffer);
 
735
            last if (!defined($expected_records_sector) or !defined($log_format));
 
736
            $record_count_sector = 0;
 
737
        }
 
738
 
 
739
        #-----------------------------------------
 
740
        # Check if all the records has been read.
 
741
        #-----------------------------------------
 
742
        if ($record_count_total >= $expected_records_total) {
 
743
            printf("Total record count: %u\n", $record_count_total);
 
744
            last;
 
745
        }
 
746
        if ($record_count_sector >= $expected_records_sector) {
 
747
            $new_offset = $SIZEOF_SECTOR * (int(tell($fp) / $SIZEOF_SECTOR) + 1);
 
748
            if ($new_offset < $log_len) {
 
749
                # Log file contains more data (that is old data, being overwritten).
 
750
                seek($fp, $new_offset, 0);
 
751
                next;
 
752
            } else {
 
753
                # End Of File.
 
754
                if (!defined($opt_b)) {
 
755
                    printf("ERROR: End of log file! Total record count: %u, expected %u\n", $record_count_total, $expected_records_total);
 
756
                    last;
 
757
                } else {
 
758
                    printf("Total record count: %u\n", $record_count_total);
 
759
                    last;
 
760
                }
 
761
            } 
 
762
        }
 
763
 
 
764
        #--------------------------------------------------------
 
765
        # Check for:
 
766
        # - record separator:  AAAAAAAAAAAAAAXXYYYYYYYYBBBBBBBB
 
767
        # - non written space: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
 
768
        #--------------------------------------------------------
 
769
        if (($log_len - tell($fp)) >= $SIZEOF_SEPARATOR) {
 
770
 
 
771
            $buffer = my_read($fp, $SIZEOF_SEPARATOR);
 
772
 
 
773
            if ((substr($buffer, 0, 7) eq (chr(0xaa) x 7)) and (substr($buffer, -4) eq (chr(0xbb) x 4))) {
 
774
                #----------------------------------------------------------
 
775
                # Found a record separator.
 
776
                #----------------------------------------------------------
 
777
                # Close the current <trk> in GPX.
 
778
                if ($gpx_in_trk) {
 
779
                    gpx_print_trk_end($fp_gpx_trk) if ($opt_t);
 
780
                    $gpx_in_trk = 0;
 
781
                }
 
782
                my $separator_type = ord(substr($buffer, 7, $SIZEOF_BYTE));
 
783
                my $separator_arg  = mtk2long(substr($buffer, 8, $SIZEOF_LONG));
 
784
                printf("Separator: %s, type: %s\n", uc(unpack('H*', $buffer)), describe_separator_type($separator_type)) if ($debug >= $LOG_INFO);
 
785
                if ($separator_type == $SEP_TYPE_CHANGE_LOG_BITMASK) {
 
786
                    $log_format = $separator_arg;
 
787
                    printf("New log bitmask: %s (0x%08X = %s)\n", $separator_arg, $log_format, describe_log_format($log_format)) if ($debug >= $LOG_INFO);
 
788
                }
 
789
                next; # Search for the next record or record separator.
 
790
 
 
791
            } elsif ($buffer eq (chr(0xff) x $SIZEOF_SEPARATOR)) {
 
792
                #----------------------------------------------------------
 
793
                # Found non-written space.
 
794
                #----------------------------------------------------------
 
795
                # Close the current <trk> in GPX.
 
796
                if ($gpx_in_trk) {
 
797
                    gpx_print_trk_end($fp_gpx_trk) if ($opt_t);
 
798
                    $gpx_in_trk = 0;
 
799
                }
 
800
                if ($expected_records_sector == 0xffff) {
 
801
                    # Sector record count = 0xffff means this is the currently writing sector.
 
802
                    # We found empty space, so we skip to sector end.
 
803
                    $new_offset = $SIZEOF_SECTOR * (int(tell($fp) / $SIZEOF_SECTOR) + 1);
 
804
                    if ($new_offset < $log_len) {
 
805
                        # Log file contains more data (that is old data, being overwritten).
 
806
                        seek($fp, $new_offset, 0);
 
807
                        next; # Search for the next record or record separator.
 
808
                    } else {
 
809
                        # End Of File.
 
810
                        printf("ERROR: End of log file. Total number of read records: %u, expected %u\n", $record_count_total, $expected_records_total);
 
811
                        last;
 
812
                    } 
 
813
                } else {
 
814
                    # ERROR! Non written space, but this is not the writing sector.
 
815
                    printf("ERROR: Non written space! Read %u records, expected %u\n", $record_count_sector, $expected_records_sector);
 
816
                    last;
 
817
                }
 
818
 
 
819
            } else {
 
820
                # None of above, should be record data: rewind the file pointer so we can read it.
 
821
                seek($fp, -$SIZEOF_SEPARATOR, 1);
 
822
            }
 
823
        }
 
824
 
 
825
        #-----------------------------------------
 
826
        # Read a log record.
 
827
        #-----------------------------------------
 
828
        $record_count_sector++;
 
829
        $record_count_total++;
 
830
        $checksum = 0;
 
831
        printf("Reading log sector: record %u (%u/%u total)\n", $record_count_sector, $record_count_total, $expected_records_total) if ($debug >= $LOG_INFO);
 
832
 
 
833
        # Read each record field.
 
834
        undef($record_utc);
 
835
        if ($log_format & $LOG_FORMAT_UTC) {
 
836
            $buffer = my_read($fp, $SIZEOF_LOG_UTC);
 
837
            $checksum ^= packet_checksum($buffer);
 
838
            $record_utc = utc_time(mtk2long($buffer));
 
839
            printf("Record UTC: %s %s\n", uc(unpack('H*', $buffer)), $record_utc) if ($debug >= $LOG_DEBUG);
 
840
        }
 
841
 
 
842
        undef($record_valid);
 
843
        if ($log_format & $LOG_FORMAT_VALID) {
 
844
            $buffer = my_read($fp, $SIZEOF_LOG_VALID);
 
845
            $checksum ^= packet_checksum($buffer);
 
846
            $record_valid = mtk2unsignedword($buffer);
 
847
            printf("Record VALID: %s (0x%04X = %s)\n", uc(unpack('H*', $buffer)), $record_valid, describe_valid_mtk($record_valid)) if ($debug >= $LOG_DEBUG);
 
848
         }
 
849
 
 
850
        undef($record_latitude);
 
851
        if ($log_format & $LOG_FORMAT_LATITUDE) {
 
852
            $buffer = my_read($fp, $SIZEOF_LOG_LATITUDE);
 
853
            $checksum ^= packet_checksum($buffer);
 
854
            $record_latitude = mtk2double($buffer);
 
855
            printf("Record LATITUDE: %s (%.9f)\n", uc(unpack('H*', $buffer)), $record_latitude) if ($debug >= $LOG_DEBUG);
 
856
        }
 
857
 
 
858
        undef($record_longitude);
 
859
        if ($log_format & $LOG_FORMAT_LONGITUDE) {
 
860
            $buffer = my_read($fp, $SIZEOF_LOG_LONGITUDE);
 
861
            $checksum ^= packet_checksum($buffer);
 
862
            $record_longitude = mtk2double($buffer);
 
863
            printf("Record LONGITUDE: %s (%.9f)\n", uc(unpack('H*', $buffer)), $record_longitude) if ($debug >= $LOG_DEBUG);
 
864
        }
 
865
 
 
866
        undef($record_height);
 
867
        if ($log_format & $LOG_FORMAT_HEIGHT) {
 
868
            $buffer = my_read($fp, $SIZEOF_LOG_HEIGHT);
 
869
            $checksum ^= packet_checksum($buffer);
 
870
            $record_height = mtk2float($buffer);
 
871
            printf("Record HEIGHT: %s (%.6f)\n", uc(unpack('H*', $buffer)), $record_height) if ($debug >= $LOG_DEBUG);
 
872
        }
 
873
 
 
874
        undef($record_speed);
 
875
        if ($log_format & $LOG_FORMAT_SPEED) {
 
876
            $buffer = my_read($fp, $SIZEOF_LOG_SPEED);
 
877
            $checksum ^= packet_checksum($buffer);
 
878
            $record_speed = mtk2float($buffer);
 
879
            printf("Record SPEED: %s (%.6f)\n", uc(unpack('H*', $buffer)), $record_speed) if ($debug >= $LOG_DEBUG);
 
880
        }
 
881
 
 
882
        undef($record_heading);
 
883
        if ($log_format & $LOG_FORMAT_HEADING) {
 
884
            $buffer = my_read($fp, $SIZEOF_LOG_HEADING);
 
885
            $checksum ^= packet_checksum($buffer);
 
886
            $record_heading = mtk2float($buffer);
 
887
            printf("Record HEADING: %s (%.6f)\n", uc(unpack('H*', $buffer)), $record_heading) if ($debug >= $LOG_DEBUG);
 
888
        }
 
889
 
 
890
        undef($record_dsta);
 
891
        if ($log_format & $LOG_FORMAT_DSTA) {
 
892
            $buffer = my_read($fp, $SIZEOF_LOG_DSTA);
 
893
            $checksum ^= packet_checksum($buffer);
 
894
            $record_dsta = mtk2unsignedword($buffer);
 
895
            printf("Record DSTA: %s (%u)\n", uc(unpack('H*', $buffer)), $record_dsta) if ($debug >= $LOG_DEBUG);
 
896
        }
 
897
 
 
898
        undef($record_dage);
 
899
        if ($log_format & $LOG_FORMAT_DAGE) {
 
900
            $buffer = my_read($fp, $SIZEOF_LOG_DAGE);
 
901
            $checksum ^= packet_checksum($buffer);
 
902
            $record_dage = mtk2long($buffer);
 
903
            printf("Record DAGE: %s (%u)\n", uc(unpack('H*', $buffer)), $record_dage) if ($debug >= $LOG_DEBUG);
 
904
        }
 
905
 
 
906
        undef($record_pdop);
 
907
        if ($log_format & $LOG_FORMAT_PDOP) {
 
908
            $buffer = my_read($fp, $SIZEOF_LOG_PDOP);
 
909
            $checksum ^= packet_checksum($buffer);
 
910
            $record_pdop = mtk2unsignedword($buffer) / 100;
 
911
            printf("Record PDOP: %s (%.2f)\n", uc(unpack('H*', $buffer)), $record_pdop) if ($debug >= $LOG_DEBUG);
 
912
        }
 
913
 
 
914
        undef($record_hdop);
 
915
        if ($log_format & $LOG_FORMAT_HDOP) {
 
916
            $buffer = my_read($fp, $SIZEOF_LOG_HDOP);
 
917
            $checksum ^= packet_checksum($buffer);
 
918
            $record_hdop = mtk2unsignedword($buffer) / 100;
 
919
            printf("Record HDOP: %s (%.2f)\n", uc(unpack('H*', $buffer)), $record_hdop) if ($debug >= $LOG_DEBUG);
 
920
        }
 
921
 
 
922
        undef($record_vdop);
 
923
        if ($log_format & $LOG_FORMAT_VDOP) {
 
924
            $buffer = my_read($fp, $SIZEOF_LOG_VDOP);
 
925
            $checksum ^= packet_checksum($buffer);
 
926
            $record_vdop = mtk2unsignedword($buffer) / 100;
 
927
            printf("Record VDOP: %s (%.2f)\n", uc(unpack('H*', $buffer)), $record_vdop) if ($debug >= $LOG_DEBUG);
 
928
        }
 
929
 
 
930
        undef($record_nsat_in_use);
 
931
        undef($record_nsat_in_view);
 
932
        if ($log_format & $LOG_FORMAT_NSAT) {
 
933
            $buffer = my_read($fp, $SIZEOF_BYTE);
 
934
            $checksum ^= packet_checksum($buffer);
 
935
            $record_nsat_in_view = mtk2byte($buffer);
 
936
            $buffer = my_read($fp, $SIZEOF_BYTE);
 
937
            $checksum ^= packet_checksum($buffer);
 
938
            $record_nsat_in_use = mtk2byte($buffer);
 
939
            printf("Record NSAT: in view %u, in use %u\n", $record_nsat_in_view, $record_nsat_in_use) if ($debug >= $LOG_DEBUG);
 
940
        }
 
941
 
 
942
        undef($record_satdata);
 
943
        if ($log_format & $LOG_FORMAT_SID) {
 
944
            my $satdata_count = 0;
 
945
            my $satdata_sid;
 
946
            my $satdata_inuse;
 
947
            my $satdata_inview;
 
948
            my $satdata_elevation;
 
949
            my $satdata_azimuth;
 
950
            my $satdata_snr;
 
951
            while (1) {
 
952
                # Even with zero satellites in view, we have at least one bunch of data.
 
953
                $buffer = my_read($fp, $SIZEOF_LOG_SID);
 
954
                $checksum ^= packet_checksum($buffer);
 
955
                $satdata_sid = mtk2byte($buffer);
 
956
                $buffer = my_read($fp, $SIZEOF_LOG_SIDINUSE);
 
957
                $checksum ^= packet_checksum($buffer);
 
958
                $satdata_inuse = mtk2byte($buffer);
 
959
                $buffer = my_read($fp, $SIZEOF_LOG_SATSINVIEW);
 
960
                $checksum ^= packet_checksum($buffer);
 
961
                $satdata_inview = mtk2unsignedword($buffer);
 
962
                if ($satdata_inview == 0) {
 
963
                    printf("No satellites in view\n") if ($debug >= $LOG_DEBUG);
 
964
                } else {
 
965
                    # Read data for this satellite.
 
966
                    printf("Sats in view: %u, SatID %u in use: %u\n", $satdata_inview, $satdata_sid, $satdata_inuse) if ($debug >= $LOG_DEBUG);
 
967
                    undef($satdata_elevation);
 
968
                    if ($log_format & $LOG_FORMAT_ELEVATION) {
 
969
                        $buffer = my_read($fp, $SIZEOF_LOG_ELEVATION);
 
970
                        $checksum ^= packet_checksum($buffer);
 
971
                        $satdata_elevation = mtk2signedword($buffer);
 
972
                        printf("Satellite ELEVATION: %s (%d)\n", uc(unpack('H*', $buffer)), $satdata_elevation) if ($debug >= $LOG_DEBUG);
 
973
                    }
 
974
                    undef($satdata_azimuth);
 
975
                    if ($log_format & $LOG_FORMAT_AZIMUTH) {
 
976
                        $buffer = my_read($fp, $SIZEOF_LOG_AZIMUTH);
 
977
                        $checksum ^= packet_checksum($buffer);
 
978
                        $satdata_azimuth = mtk2unsignedword($buffer);
 
979
                        printf("Satellite AZIMUTH:   %s (%u)\n", uc(unpack('H*', $buffer)), $satdata_azimuth) if ($debug >= $LOG_DEBUG);
 
980
                    }
 
981
                    undef($satdata_snr);
 
982
                    if ($log_format & $LOG_FORMAT_SNR) {
 
983
                        $buffer = my_read($fp, $SIZEOF_LOG_SNR);
 
984
                        $checksum ^= packet_checksum($buffer);
 
985
                        $satdata_snr = mtk2unsignedword($buffer);
 
986
                        printf("Satellite SNR:       %s (%u)\n", uc(unpack('H*', $buffer)), $satdata_snr) if ($debug >= $LOG_DEBUG);
 
987
                    }
 
988
                    $record_satdata .= "\n" if ($record_satdata ne '');
 
989
                    $record_satdata .= $satdata_sid       . "\t";
 
990
                    $record_satdata .= $satdata_inuse     . "\t";
 
991
                    $record_satdata .= $satdata_elevation . "\t";
 
992
                    $record_satdata .= $satdata_azimuth   . "\t";
 
993
                    $record_satdata .= $satdata_snr;
 
994
                    $satdata_count++;
 
995
                }
 
996
                last if ($satdata_count >= $satdata_inview);
 
997
            }
 
998
        }
 
999
 
 
1000
        undef($record_rcr);
 
1001
        if ($log_format & $LOG_FORMAT_RCR) {
 
1002
            $buffer = my_read($fp, $SIZEOF_LOG_RCR);
 
1003
            $checksum ^= packet_checksum($buffer);
 
1004
            $record_rcr = mtk2unsignedword($buffer);
 
1005
            printf("Record RCR: %s (%s)\n", uc(unpack('H*', $buffer)), describe_rcr_mtk($record_rcr)) if ($debug >= $LOG_DEBUG);
 
1006
        }
 
1007
 
 
1008
        undef($record_millisecond);
 
1009
        if ($log_format & $LOG_FORMAT_MILLISECOND) {
 
1010
            $buffer = my_read($fp, $SIZEOF_LOG_MILLISECOND);
 
1011
            $checksum ^= packet_checksum($buffer);
 
1012
            $record_millisecond = mtk2unsignedword($buffer);
 
1013
            printf("Record MILLISECOND: %s (%u)\n", uc(unpack('H*', $buffer)), $record_millisecond) if ($debug >= $LOG_DEBUG);
 
1014
        }
 
1015
 
 
1016
        undef($record_distance);
 
1017
        if ($log_format & $LOG_FORMAT_DISTANCE) {
 
1018
            $buffer = my_read($fp, $SIZEOF_LOG_DISTANCE);
 
1019
            $checksum ^= packet_checksum($buffer);
 
1020
            $record_distance = mtk2double($buffer);
 
1021
            printf("Record DISTANCE: %s (%.9f)\n", uc(unpack('H*', $buffer)), $record_distance) if ($debug >= $LOG_DEBUG);
 
1022
        }
 
1023
 
 
1024
        # Read and verify checksum.
 
1025
        $buffer = my_read($fp, $SIZEOF_BYTE);
 
1026
        if ($buffer ne '*') {
 
1027
            printf("ERROR: Checksum separator error: expected char 0x%02X, found 0x%02X\n", ord('*'), ord($buffer));
 
1028
            last;
 
1029
        }
 
1030
        $buffer = my_read($fp, $SIZEOF_BYTE);
 
1031
        if ($checksum != ord($buffer)) {
 
1032
            printf("ERROR: Record checksum error: expected 0x%02X, computed 0x%02X\n", ord($buffer), $checksum);
 
1033
            last;
 
1034
        }
 
1035
 
 
1036
        # Start a new GPX <trkseg> on satellite lost.
 
1037
        if (($record_valid == $VALID_NOFIX) and $gpx_in_trk) {
 
1038
            gpx_print_trk_end($fp_gpx_trk) if ($opt_t);
 
1039
            $gpx_in_trk = 0;
 
1040
        }
 
1041
 
 
1042
        if (defined($record_latitude) and defined($record_longitude)) {
 
1043
            # Write <trkpt> data in GPX file.
 
1044
            if (($record_valid != $VALID_NOFIX) and !($record_rcr & $RCR_BUTTON)) {
 
1045
                if (! $gpx_in_trk) {
 
1046
                    gpx_print_trk_begin($fp_gpx_trk) if ($opt_t);
 
1047
                    $gpx_in_trk = 1;
 
1048
                }
 
1049
                gpx_print_trkpt($fp_gpx_trk) if ($opt_t);
 
1050
            }
 
1051
            # Write <wpt> data in GPX file.
 
1052
            if (($record_rcr & $RCR_BUTTON) and ($record_valid != $VALID_NOFIX)) {
 
1053
                gpx_print_wpt($fp_gpx_wpt) if ($opt_w);
 
1054
            }
 
1055
        }
 
1056
 
 
1057
    }
 
1058
    close($fp);
 
1059
 
 
1060
    # Eventually close the <trk> GPX tags.
 
1061
    if ($gpx_in_trk) {
 
1062
        gpx_print_trk_end($fp_gpx_trk) if ($opt_t);
 
1063
        $gpx_in_trk = 0;
 
1064
    }
 
1065
 
 
1066
    # Write GPX tracks file.
 
1067
    if ($opt_t) {
 
1068
        close($fp_gpx_trk);
 
1069
        open($fp_gpx, ">$gpx_trk_fname") or die("ERROR writing $gpx_trk_fname: $!");
 
1070
        gpx_print_gpx_begin($fp_gpx, time(), $gpx_trk_minlat, $gpx_trk_minlon, $gpx_trk_maxlat, $gpx_trk_maxlon);
 
1071
        open($fp_gpx_trk, "$gpx_trk_tmp_fname") or die;
 
1072
        while (<$fp_gpx_trk>) { print $fp_gpx $_; }
 
1073
        close($fp_gpx_trk);
 
1074
        gpx_print_gpx_end($fp_gpx);
 
1075
        close($fp_gpx);
 
1076
        unlink($gpx_trk_tmp_fname);
 
1077
    }
 
1078
 
 
1079
    # Write GPX waypoints file.
 
1080
    if ($opt_w) {
 
1081
        close($fp_gpx_wpt);
 
1082
        open($fp_gpx, ">$gpx_wpt_fname") or die("ERROR writing $gpx_wpt_fname: $!");
 
1083
        gpx_print_gpx_begin($fp_gpx, time(), $gpx_wpt_minlat, $gpx_wpt_minlon, $gpx_wpt_maxlat, $gpx_wpt_maxlon);
 
1084
        open($fp_gpx_wpt, "$gpx_wpt_tmp_fname") or die;
 
1085
        while (<$fp_gpx_wpt>) { print $fp_gpx $_; }
 
1086
        close($fp_gpx_wpt);
 
1087
        gpx_print_gpx_end($fp_gpx);
 
1088
        close($fp_gpx);
 
1089
        unlink($gpx_wpt_tmp_fname);
 
1090
    }
 
1091
}
 
1092
 
 
1093
#-------------------------------------------------------------------------
 
1094
# Read some bytes from the device and return them.
 
1095
#-------------------------------------------------------------------------
 
1096
sub my_read {
 
1097
    my $handle   = shift;
 
1098
    my $length   = shift;
 
1099
    my $variable;
 
1100
    my $n = read($handle, $variable, $length);
 
1101
    printf("ERROR: Reading file: read %u bytes, expected %u\n", $n, $length) if ($n != $length);
 
1102
    return($variable);
 
1103
}
 
1104
 
 
1105
#-------------------------------------------------------------------------
 
1106
# Parse the header (0x200 bytes) of a datalog sector (every 0x10000 bytes).
 
1107
# Return the number of records in the sector and the log format.
 
1108
#-------------------------------------------------------------------------
 
1109
sub parse_sector_header {
 
1110
 
 
1111
    my $sector_header = shift;
 
1112
 
 
1113
    if ($debug >= $LOG_NOTICE) {
 
1114
        printf("\n");
 
1115
        printf("Sector header:      %s\n", uc(unpack('H*', $sector_header))) if ($debug >= $LOG_DEBUG);
 
1116
    }
 
1117
 
 
1118
    # Check validity of sector header.
 
1119
    my $separator   =     substr($sector_header, -6, 1);              # Should be '*'
 
1120
    my $checksum    = ord(substr($sector_header, -5, $SIZEOF_BYTE));  # WARNING: It's not the XOR checksum!!!
 
1121
    my $header_tail =     substr($sector_header, -4, 4);              # Should be 0xBBBBBBBB
 
1122
    if (($separator ne '*') or ($header_tail ne (chr(0xBB) x 4))) {
 
1123
        printf("ERROR: Invalid datalog sector header\n");
 
1124
        return(undef, undef);
 
1125
    }
 
1126
 
 
1127
    # Settings of this log sector (hex values, LSB first).
 
1128
    my $log_count    = substr($sector_header,  0, $SIZEOF_WORD);      # Record count in this sector. 0xFFFF if the sector is not filled.
 
1129
    my $log_format   = substr($sector_header,  2, $SIZEOF_LONG);      # Log format bitmask
 
1130
    my $log_status   = substr($sector_header,  6, $SIZEOF_WORD);      # Log mode bitmask
 
1131
    my $log_period   = substr($sector_header,  8, $SIZEOF_LONG);      # Log period   in 10ths of s
 
1132
    my $log_distance = substr($sector_header, 12, $SIZEOF_LONG);      # Log distance in 10ths of m
 
1133
    my $log_speed    = substr($sector_header, 16, $SIZEOF_LONG);      # Log speed    in 10ths ok km/h
 
1134
    my $log_failsect = substr($sector_header, 20, $SIZEOF_BYTE * 32); # Failed sectors bitmask
 
1135
 
 
1136
    my $count    = unpack('S', $log_count);
 
1137
    my $format   = unpack('L', $log_format);
 
1138
    my $status   = unpack('S', $log_status);
 
1139
    my $period   = unpack('L', $log_period);
 
1140
    my $distance = unpack('L', $log_distance);
 
1141
    my $speed    = unpack('L', $log_speed);
 
1142
 
 
1143
    if ($debug >= $LOG_NOTICE) {
 
1144
        printf("Record count:        %s %s %u records\n",  uc(unpack('H*', $log_count)),    ' 'x6, $count);
 
1145
        printf("Log format mask:     %s %s %032bb (%s)\n", uc(unpack('H*', $log_format)),   ' 'x2, $format, describe_log_format($format));
 
1146
        printf("Log mode mask:       %s %s %016bb (%s)\n", uc(unpack('H*', $log_status)),   ' 'x6, $status, describe_log_status($status));
 
1147
        printf("Log period:          %s %s %6.2f s\n",     uc(unpack('H*', $log_period)),   ' 'x2, $period   / 10);
 
1148
        printf("Log distance:        %s %s %6.2f m\n",     uc(unpack('H*', $log_distance)), ' 'x2, $distance / 10);
 
1149
        printf("Log speed:           %s %s %6.2f km/h\n",  uc(unpack('H*', $log_speed)),    ' 'x2, $speed    / 10);
 
1150
        printf("Failed sectors mask: %s\n",                uc(unpack('H*', $log_failsect)));
 
1151
        printf("\n");
 
1152
    }
 
1153
 
 
1154
    return($count, $format);
 
1155
}
 
1156
 
 
1157
#-------------------------------------------------------------------------
 
1158
# Given a log format value (bitmask), return a description string.
 
1159
#-------------------------------------------------------------------------
 
1160
sub describe_log_format {
 
1161
 
 
1162
    my $log_format = shift;
 
1163
    my $str = '';
 
1164
 
 
1165
    $str .= ',UTC'         if ($log_format & $LOG_FORMAT_UTC);
 
1166
    $str .= ',VALID'       if ($log_format & $LOG_FORMAT_VALID);
 
1167
    $str .= ',LATITUDE'    if ($log_format & $LOG_FORMAT_LATITUDE);
 
1168
    $str .= ',LONGITUDE'   if ($log_format & $LOG_FORMAT_LONGITUDE);
 
1169
    $str .= ',HEIGHT'      if ($log_format & $LOG_FORMAT_HEIGHT);
 
1170
    $str .= ',SPEED'       if ($log_format & $LOG_FORMAT_SPEED);
 
1171
    $str .= ',HEADING'     if ($log_format & $LOG_FORMAT_HEADING);
 
1172
    $str .= ',DSTA'        if ($log_format & $LOG_FORMAT_DSTA);
 
1173
    $str .= ',DAGE'        if ($log_format & $LOG_FORMAT_DAGE);
 
1174
    $str .= ',PDOP'        if ($log_format & $LOG_FORMAT_PDOP);
 
1175
    $str .= ',HDOP'        if ($log_format & $LOG_FORMAT_HDOP);
 
1176
    $str .= ',VDOP'        if ($log_format & $LOG_FORMAT_VDOP);
 
1177
    $str .= ',NSAT'        if ($log_format & $LOG_FORMAT_NSAT);
 
1178
    $str .= ',SID'         if ($log_format & $LOG_FORMAT_SID);
 
1179
    $str .= ',ELEVATION'   if ($log_format & $LOG_FORMAT_ELEVATION);
 
1180
    $str .= ',AZIMUTH'     if ($log_format & $LOG_FORMAT_AZIMUTH);
 
1181
    $str .= ',SNR'         if ($log_format & $LOG_FORMAT_SNR);
 
1182
    $str .= ',RCR'         if ($log_format & $LOG_FORMAT_RCR);
 
1183
    $str .= ',MILLISECOND' if ($log_format & $LOG_FORMAT_MILLISECOND);
 
1184
    $str .= ',DISTANCE'    if ($log_format & $LOG_FORMAT_DISTANCE);
 
1185
 
 
1186
    return(substr($str, 1));
 
1187
}
 
1188
 
 
1189
#-------------------------------------------------------------------------
 
1190
#
 
1191
#-------------------------------------------------------------------------
 
1192
sub encode_log_format {
 
1193
 
 
1194
    my $log_format = shift;
 
1195
    my $changes = shift;
 
1196
 
 
1197
    $log_format |= $LOG_FORMAT_UTC          if ($changes =~ m/\bUTC\b/);
 
1198
    $log_format |= $LOG_FORMAT_VALID        if ($changes =~ m/\bVALID\b/);
 
1199
    $log_format |= $LOG_FORMAT_LATITUDE     if ($changes =~ m/\bLATITUDE\b/);
 
1200
    $log_format |= $LOG_FORMAT_LONGITUDE    if ($changes =~ m/\bLONGITUDE\b/);
 
1201
    $log_format |= $LOG_FORMAT_HEIGHT       if ($changes =~ m/\bHEIGHT\b/);
 
1202
    $log_format |= $LOG_FORMAT_SPEED        if ($changes =~ m/\bSPEED\b/);
 
1203
    $log_format |= $LOG_FORMAT_HEADING      if ($changes =~ m/\bHEADING\b/);
 
1204
    $log_format |= $LOG_FORMAT_DSTA         if ($changes =~ m/\bDSTA\b/);
 
1205
    $log_format |= $LOG_FORMAT_DAGE         if ($changes =~ m/\bDAGE\b/);
 
1206
    $log_format |= $LOG_FORMAT_PDOP         if ($changes =~ m/\bPDOP\b/);
 
1207
    $log_format |= $LOG_FORMAT_HDOP         if ($changes =~ m/\bHDOP\b/);
 
1208
    $log_format |= $LOG_FORMAT_VDOP         if ($changes =~ m/\bVDOP\b/);
 
1209
    $log_format |= $LOG_FORMAT_NSAT         if ($changes =~ m/\bNSAT\b/);
 
1210
    $log_format |= $LOG_FORMAT_SID          if ($changes =~ m/\bSID\b/);
 
1211
    $log_format |= $LOG_FORMAT_ELEVATION    if ($changes =~ m/\bELEVATION\b/);
 
1212
    $log_format |= $LOG_FORMAT_AZIMUTH      if ($changes =~ m/\bAZIMUTH\b/);
 
1213
    $log_format |= $LOG_FORMAT_SNR          if ($changes =~ m/\bSNR\b/);
 
1214
    $log_format |= $LOG_FORMAT_RCR          if ($changes =~ m/\bRCR\b/);
 
1215
    $log_format |= $LOG_FORMAT_MILLISECOND  if ($changes =~ m/\bMILLISECOND\b/);
 
1216
    $log_format |= $LOG_FORMAT_DISTANCE     if ($changes =~ m/\bDISTANCE\b/);
 
1217
 
 
1218
    $log_format &= ~$LOG_FORMAT_UTC         if ($changes =~ m/-UTC\b/);
 
1219
    $log_format &= ~$LOG_FORMAT_VALID       if ($changes =~ m/-VALID\b/);
 
1220
    $log_format &= ~$LOG_FORMAT_LATITUDE    if ($changes =~ m/-LATITUDE\b/);
 
1221
    $log_format &= ~$LOG_FORMAT_LONGITUDE   if ($changes =~ m/-LONGITUDE\b/);
 
1222
    $log_format &= ~$LOG_FORMAT_HEIGHT      if ($changes =~ m/-HEIGHT\b/);
 
1223
    $log_format &= ~$LOG_FORMAT_SPEED       if ($changes =~ m/-SPEED\b/);
 
1224
    $log_format &= ~$LOG_FORMAT_HEADING     if ($changes =~ m/-HEADING\b/);
 
1225
    $log_format &= ~$LOG_FORMAT_DSTA        if ($changes =~ m/-DSTA\b/);
 
1226
    $log_format &= ~$LOG_FORMAT_DAGE        if ($changes =~ m/-DAGE\b/);
 
1227
    $log_format &= ~$LOG_FORMAT_PDOP        if ($changes =~ m/-PDOP\b/);
 
1228
    $log_format &= ~$LOG_FORMAT_HDOP        if ($changes =~ m/-HDOP\b/);
 
1229
    $log_format &= ~$LOG_FORMAT_VDOP        if ($changes =~ m/-VDOP\b/);
 
1230
    $log_format &= ~$LOG_FORMAT_NSAT        if ($changes =~ m/-NSAT\b/);
 
1231
    $log_format &= ~$LOG_FORMAT_SID         if ($changes =~ m/-SID\b/);
 
1232
    $log_format &= ~$LOG_FORMAT_ELEVATION   if ($changes =~ m/-ELEVATION\b/);
 
1233
    $log_format &= ~$LOG_FORMAT_AZIMUTH     if ($changes =~ m/-AZIMUTH\b/);
 
1234
    $log_format &= ~$LOG_FORMAT_SNR         if ($changes =~ m/-SNR\b/);
 
1235
    $log_format &= ~$LOG_FORMAT_RCR         if ($changes =~ m/-RCR\b/);
 
1236
    $log_format &= ~$LOG_FORMAT_MILLISECOND if ($changes =~ m/-MILLISECOND\b/);
 
1237
    $log_format &= ~$LOG_FORMAT_DISTANCE    if ($changes =~ m/-DISTANCE\b/);
 
1238
 
 
1239
    return($log_format);
 
1240
}
 
1241
 
 
1242
#-------------------------------------------------------------------------
 
1243
# Given a log format value (bitmask), return the record size in bytes.
 
1244
#-------------------------------------------------------------------------
 
1245
sub sizeof_log_format {
 
1246
 
 
1247
    my $log_format = shift;
 
1248
    my $size_wpt = 0;
 
1249
    my $size_sat = 0;
 
1250
 
 
1251
    $size_wpt += $SIZEOF_LOG_UTC         if ($log_format & $LOG_FORMAT_UTC);
 
1252
    $size_wpt += $SIZEOF_LOG_VALID       if ($log_format & $LOG_FORMAT_VALID);
 
1253
    $size_wpt += $SIZEOF_LOG_LATITUDE    if ($log_format & $LOG_FORMAT_LATITUDE);
 
1254
    $size_wpt += $SIZEOF_LOG_LONGITUDE   if ($log_format & $LOG_FORMAT_LONGITUDE);
 
1255
    $size_wpt += $SIZEOF_LOG_HEIGHT      if ($log_format & $LOG_FORMAT_HEIGHT);
 
1256
    $size_wpt += $SIZEOF_LOG_SPEED       if ($log_format & $LOG_FORMAT_SPEED);
 
1257
    $size_wpt += $SIZEOF_LOG_HEADING     if ($log_format & $LOG_FORMAT_HEADING);
 
1258
    $size_wpt += $SIZEOF_LOG_DSTA        if ($log_format & $LOG_FORMAT_DSTA);
 
1259
    $size_wpt += $SIZEOF_LOG_DAGE        if ($log_format & $LOG_FORMAT_DAGE);
 
1260
    $size_wpt += $SIZEOF_LOG_PDOP        if ($log_format & $LOG_FORMAT_PDOP);
 
1261
    $size_wpt += $SIZEOF_LOG_HDOP        if ($log_format & $LOG_FORMAT_HDOP);
 
1262
    $size_wpt += $SIZEOF_LOG_VDOP        if ($log_format & $LOG_FORMAT_VDOP);
 
1263
    $size_wpt += $SIZEOF_LOG_NSAT        if ($log_format & $LOG_FORMAT_NSAT);
 
1264
 
 
1265
    # Variable part, for each satellite:
 
1266
    if ($log_format & $LOG_FORMAT_SID) {
 
1267
        $size_sat += $SIZEOF_LOG_SID;
 
1268
        $size_sat += $SIZEOF_LOG_SIDINUSE;
 
1269
        $size_sat += $SIZEOF_LOG_SATSINVIEW;
 
1270
        $size_sat += $SIZEOF_LOG_ELEVATION   if ($log_format & $LOG_FORMAT_ELEVATION);
 
1271
        $size_sat += $SIZEOF_LOG_AZIMUTH     if ($log_format & $LOG_FORMAT_AZIMUTH);
 
1272
        $size_sat += $SIZEOF_LOG_SNR         if ($log_format & $LOG_FORMAT_SNR);
 
1273
    }
 
1274
 
 
1275
    $size_wpt += $SIZEOF_LOG_RCR         if ($log_format & $LOG_FORMAT_RCR);
 
1276
    $size_wpt += $SIZEOF_LOG_MILLISECOND if ($log_format & $LOG_FORMAT_MILLISECOND);
 
1277
    $size_wpt += $SIZEOF_LOG_DISTANCE    if ($log_format & $LOG_FORMAT_DISTANCE);
 
1278
 
 
1279
    return($size_wpt, $size_sat);
 
1280
 
 
1281
}
 
1282
 
 
1283
#-------------------------------------------------------------------------
 
1284
# Return a string describing the log record separator type.
 
1285
#-------------------------------------------------------------------------
 
1286
sub describe_separator_type {
 
1287
    my $sep_type = shift;
 
1288
    return('CHANGE_LOG_BITMASK')    if ($sep_type == $SEP_TYPE_CHANGE_LOG_BITMASK);
 
1289
    return('CHANGE_LOG_PERIOD')     if ($sep_type == $SEP_TYPE_CHANGE_LOG_PERIOD);
 
1290
    return('CHANGE_LOG_DISTANCE')   if ($sep_type == $SEP_TYPE_CHANGE_LOG_DISTANCE);
 
1291
    return('CHANGE_LOG_SPEED')      if ($sep_type == $SEP_TYPE_CHANGE_LOG_SPEED);
 
1292
    return('CHANGE_OVERLAP_STOP')   if ($sep_type == $SEP_TYPE_CHANGE_OVERLAP_STOP);
 
1293
    return('CHANGE_START_STOP_LOG') if ($sep_type == $SEP_TYPE_CHANGE_START_STOP_LOG);
 
1294
    return('Unknown');
 
1295
}
 
1296
 
 
1297
#-------------------------------------------------------------------------
 
1298
# Return a string describing some field.
 
1299
#-------------------------------------------------------------------------
 
1300
sub describe_valid_mtk {
 
1301
    my $valid = shift;
 
1302
    return('nofix')     if ($valid == $VALID_NOFIX);
 
1303
    return('sps')       if ($valid == $VALID_SPS);
 
1304
    return('dgps')      if ($valid == $VALID_DGPS);
 
1305
    return('pps')       if ($valid == $VALID_PPS);
 
1306
    return('rtk')       if ($valid == $VALID_RTK);
 
1307
    return('frtk')      if ($valid == $VALID_FRTK);
 
1308
    return('estimated') if ($valid == $VALID_ESTIMATED);
 
1309
    return('manual')    if ($valid == $VALID_MANUAL);
 
1310
    return('simulator') if ($valid == $VALID_SIMULATOR);
 
1311
    return('Unknown');
 
1312
}
 
1313
 
 
1314
# Description suitable for GPX <trkpt> <fix> element.
 
1315
sub describe_valid_gpx {
 
1316
    my $valid = shift;
 
1317
    return('none')         if ($valid == $VALID_NOFIX);
 
1318
    return('3d')           if ($valid == $VALID_SPS);
 
1319
    return('dgps')         if ($valid == $VALID_DGPS);
 
1320
    return('pps')          if ($valid == $VALID_PPS);
 
1321
    return(undef);
 
1322
}
 
1323
 
 
1324
sub describe_rcr_mtk {
 
1325
    my $rcr = shift;
 
1326
    my $str = '';
 
1327
 
 
1328
    $str .= ',TIME'     if ($rcr & $RCR_TIME);
 
1329
    $str .= ',SPEED'    if ($rcr & $RCR_SPEED);
 
1330
    $str .= ',DISTANCE' if ($rcr & $RCR_DISTANCE);
 
1331
    $str .= ',BUTTON'   if ($rcr & $RCR_BUTTON);
 
1332
 
 
1333
    return('Unknown') if ($str eq '');
 
1334
    return(substr($str, 1));
 
1335
}
 
1336
 
 
1337
# Description suitable for GPX <trkpt> <type> element.
 
1338
sub describe_rcr_gpx {
 
1339
    my $rcr = shift;
 
1340
    my $str = '';
 
1341
 
 
1342
    $str .= ',TIME'     if ($rcr & $RCR_TIME);
 
1343
    $str .= ',SPEED'    if ($rcr & $RCR_SPEED);
 
1344
    $str .= ',DISTANCE' if ($rcr & $RCR_DISTANCE);
 
1345
    $str .= ',BUTTON'   if ($rcr & $RCR_BUTTON);
 
1346
 
 
1347
    return(undef) if ($str eq '');
 
1348
    return(substr($str, 1));
 
1349
}
 
1350
 
 
1351
sub describe_log_status {
 
1352
    my $log_status = shift;
 
1353
    my $str = '';
 
1354
    if ($log_status & $LOG_STATUS_AUTOLOG)        { $str .= ',AUTOLOG_ON'; }     else { $str .= ',AUTOLOG_OFF'; }
 
1355
    if ($log_status & $LOG_STATUS_STOP_WHEN_FULL) { $str .= ',STOP_WHEN_FULL'; } else { $str .= ',OVERLAP_WHEN_FULL'; }
 
1356
    if ($log_status & $LOG_STATUS_ENABLE)         { $str .= ',ENABLE_LOG'; }
 
1357
    if ($log_status & $LOG_STATUS_DISABLE)        { $str .= ',DISABLE_LOG'; }
 
1358
    if ($log_status & $LOG_STATUS_NEED_FORMAT)    { $str .= ',NEED_FORMAT'; }
 
1359
    if ($log_status & $LOG_STATUS_FULL)           { $str .= ',FULL'; }
 
1360
    return(substr($str, 1));
 
1361
}
 
1362
 
 
1363
sub describe_recording_method {
 
1364
    my $val = shift;
 
1365
    return('OVERLAP') if ($val == $RCD_METHOD_OVP);
 
1366
    return('STOP')      if ($val == $RCD_METHOD_STP);
 
1367
    return('Unknown');
 
1368
}
 
1369
 
 
1370
#-------------------------------------------------------------------------
 
1371
# Silly function: return a byte!
 
1372
#-------------------------------------------------------------------------
 
1373
sub mtk2byte {
 
1374
    ord(shift);
 
1375
}
 
1376
 
 
1377
#-------------------------------------------------------------------------
 
1378
# Convert a 16 bit binary data into an integer.
 
1379
#-------------------------------------------------------------------------
 
1380
sub mtk2unsignedword {
 
1381
    unpack('S', shift);
 
1382
}
 
1383
sub mtk2signedword{
 
1384
    unpack('s', shift);
 
1385
}
 
1386
 
 
1387
#-------------------------------------------------------------------------
 
1388
# Convert a 32 bit binary data into a long integer number.
 
1389
#-------------------------------------------------------------------------
 
1390
sub mtk2long {
 
1391
    unpack('L', shift);
 
1392
}
 
1393
 
 
1394
#-------------------------------------------------------------------------
 
1395
# Convert a 32 bit binary data into a float number.
 
1396
#-------------------------------------------------------------------------
 
1397
sub mtk2float {
 
1398
    unpack('f', shift);
 
1399
}
 
1400
 
 
1401
#-------------------------------------------------------------------------
 
1402
# Convert a 64 bit binary data into a double precision number.
 
1403
#-------------------------------------------------------------------------
 
1404
sub mtk2double {
 
1405
    unpack('d', shift);
 
1406
}
 
1407
 
 
1408
#-------------------------------------------------------------------------
 
1409
# Convert seconds from epoch (long int) to UTC timestamp. 
 
1410
#-------------------------------------------------------------------------
 
1411
sub utc_time {
 
1412
    time2str('%Y-%m-%dT%H:%M:%SZ', shift, 'GMT');
 
1413
}
 
1414
 
 
1415
#-------------------------------------------------------------------------
 
1416
# Print the header of a GPX file.
 
1417
#-------------------------------------------------------------------------
 
1418
sub gpx_print_gpx_begin {
 
1419
    my $fp     = shift;
 
1420
    my $time   = shift;
 
1421
    my $minlat = shift;
 
1422
    my $minlon = shift;
 
1423
    my $maxlat = shift;
 
1424
    my $maxlon = shift;
 
1425
    print $fp sprintf('<?xml version="1.0" encoding="UTF-8"?>%s', $GPX_EOL);
 
1426
    print $fp '<gpx' . $GPX_EOL;
 
1427
    print $fp '  version="1.1"' . $GPX_EOL;
 
1428
    print $fp '  creator="MTKBabel - http://www.rigacci.org/"' . $GPX_EOL;
 
1429
    print $fp '  xmlns="http://www.topografix.com/GPX/1/1"' . $GPX_EOL;
 
1430
    print $fp '  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' . $GPX_EOL;
 
1431
    print $fp '  xmlns:mtk="http://www.rigacci.org/gpx/MtkExtensions/v1"' . $GPX_EOL;
 
1432
    print $fp '  xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd' . $GPX_EOL;
 
1433
    print $fp '                      http://www.rigacci.org/gpx/MtkExtensions/v1 http://www.rigacci.org/gpx/MtkExtensions/v1/MtkExtensionsv1.xsd">' . $GPX_EOL;
 
1434
    print $fp sprintf('<metadata>%s', $GPX_EOL);
 
1435
    print $fp sprintf('  <time>%s</time>%s', utc_time($time), $GPX_EOL);
 
1436
    print $fp sprintf('  <bounds minlat="%.9f" minlon="%.9f" maxlat="%.9f" maxlon="%.9f"/>%s', $minlat, $minlon, $maxlat, $maxlon, $GPX_EOL);
 
1437
    print $fp sprintf('</metadata>%s', $GPX_EOL);
 
1438
}
 
1439
sub gpx_print_gpx_end {
 
1440
    my $fp = shift;
 
1441
    print $fp sprintf('</gpx>%s', $GPX_EOL);
 
1442
}
 
1443
 
 
1444
#-------------------------------------------------------------------------
 
1445
# Open and close the GPX <trk> tag.
 
1446
#-------------------------------------------------------------------------
 
1447
sub gpx_print_trk_begin {
 
1448
    my $fp = shift;
 
1449
    print $fp sprintf('<trk>%s', $GPX_EOL);
 
1450
    print $fp sprintf('  <name>%s</name>%s', $record_utc, $GPX_EOL);
 
1451
    print $fp sprintf('  <number>%u</number>%s', $gpx_trk_number, $GPX_EOL) if ($gpx_trk_number > 0);;
 
1452
    print $fp sprintf('<trkseg>%s', $GPX_EOL);
 
1453
    $gpx_trk_number++;
 
1454
}
 
1455
sub gpx_print_trk_end {
 
1456
    my $fp = shift;
 
1457
    print $fp sprintf('</trkseg>%s', $GPX_EOL);
 
1458
    print $fp sprintf('</trk>%s', $GPX_EOL);
 
1459
}
 
1460
 
 
1461
#-------------------------------------------------------------------------
 
1462
# Print a GPX <trkpt>.
 
1463
#-------------------------------------------------------------------------
 
1464
sub gpx_print_trkpt {
 
1465
    my $fp = shift;
 
1466
    print $fp sprintf('<trkpt lat="%.9f" lon="%.9f">%s', $record_latitude, $record_longitude, $GPX_EOL);
 
1467
    print $fp sprintf('  <ele>%.6f</ele>%s', $record_height, $GPX_EOL) if (defined($record_height));
 
1468
    print $fp sprintf('  <time>%s</time>%s', $record_utc,    $GPX_EOL) if (defined($record_utc));
 
1469
    gpx_print_pt_attributes($fp);
 
1470
    print $fp sprintf('</trkpt>%s', $GPX_EOL);
 
1471
    $gpx_trk_minlat = $record_latitude  if ($record_latitude  < $gpx_trk_minlat);
 
1472
    $gpx_trk_maxlat = $record_latitude  if ($record_latitude  > $gpx_trk_maxlat);
 
1473
    $gpx_trk_minlon = $record_longitude if ($record_longitude < $gpx_trk_minlon);
 
1474
    $gpx_trk_maxlon = $record_longitude if ($record_longitude > $gpx_trk_maxlon);
 
1475
}
 
1476
 
 
1477
#-------------------------------------------------------------------------
 
1478
# Print a GPX <wpt>.
 
1479
#-------------------------------------------------------------------------
 
1480
sub gpx_print_wpt {
 
1481
    my $fp = shift;
 
1482
    $gpx_wpt_number++;
 
1483
    print $fp sprintf('<wpt lat="%.9f" lon="%.9f">%s', $record_latitude, $record_longitude, $GPX_EOL);
 
1484
    print $fp sprintf('  <ele>%.6f</ele>%s',   $record_height, $GPX_EOL) if (defined($record_height));
 
1485
    print $fp sprintf('  <time>%s</time>%s',   $record_utc,    $GPX_EOL) if (defined($record_utc));
 
1486
    print $fp sprintf('  <name>%03d</name>%s', $gpx_wpt_number, $GPX_EOL);
 
1487
    print $fp sprintf('  <cmt>%03d</cmt>%s',   $gpx_wpt_number, $GPX_EOL);
 
1488
    print $fp sprintf('  <desc>%s</desc>%s',   $record_utc,     $GPX_EOL) if (defined($record_utc));
 
1489
    print $fp sprintf('  <sym>Flag</sym>%s',                    $GPX_EOL);
 
1490
    gpx_print_pt_attributes($fp);
 
1491
    print $fp sprintf('</wpt>%s', $GPX_EOL);
 
1492
    $gpx_wpt_minlat = $record_latitude  if ($record_latitude  < $gpx_wpt_minlat);
 
1493
    $gpx_wpt_maxlat = $record_latitude  if ($record_latitude  > $gpx_wpt_maxlat);
 
1494
    $gpx_wpt_minlon = $record_longitude if ($record_longitude < $gpx_wpt_minlon);
 
1495
    $gpx_wpt_maxlon = $record_longitude if ($record_longitude > $gpx_wpt_maxlon);
 
1496
}
 
1497
 
 
1498
#-------------------------------------------------------------------------
 
1499
# Print <trkpt> and <wpt> common attributes.
 
1500
#-------------------------------------------------------------------------
 
1501
sub gpx_print_pt_attributes {
 
1502
    my $fp = shift;
 
1503
    print $fp sprintf('  <type>%s</type>%s',  describe_rcr_gpx($record_rcr),         $GPX_EOL) if (describe_rcr_gpx($record_rcr));
 
1504
    print $fp sprintf('  <fix>%s</fix>%s',  describe_valid_gpx($record_valid),       $GPX_EOL) if (describe_valid_gpx($record_valid));
 
1505
    print $fp sprintf('  <sat>%u</sat>%s',                     $record_nsat_in_use,  $GPX_EOL) if (defined($record_nsat_in_use));
 
1506
    print $fp sprintf('  <hdop>%.2f</hdop>%s',                 $record_hdop,         $GPX_EOL) if (defined($record_hdop));
 
1507
    print $fp sprintf('  <vdop>%.2f</vdop>%s',                 $record_vdop,         $GPX_EOL) if (defined($record_vdop));
 
1508
    print $fp sprintf('  <pdop>%.2f</pdop>%s',                 $record_pdop,         $GPX_EOL) if (defined($record_pdop));
 
1509
    print $fp sprintf('  <ageofdgpsdata>%u</ageofdgpsdata>%s', $record_dage,         $GPX_EOL) if (defined($record_dage));
 
1510
    print $fp sprintf('  <dgpsid>%u</dgpsid>%s',               $record_dsta,         $GPX_EOL) if (defined($record_dsta));
 
1511
 
 
1512
    if (defined($record_speed) or
 
1513
        defined($record_heading) or
 
1514
        defined($record_nsat_in_view) or
 
1515
        defined($record_millisecond) or
 
1516
        defined($record_distance) or
 
1517
        defined($record_satdata)) {
 
1518
 
 
1519
    print $fp sprintf('  <extensions>%s', $GPX_EOL);
 
1520
    print $fp sprintf('    <mtk:wptExtension>%s', $GPX_EOL);
 
1521
    print $fp sprintf('      <mtk:valid>%s</mtk:valid>%s',  describe_valid_mtk($record_valid),       $GPX_EOL) if (defined($record_valid));
 
1522
    print $fp sprintf('      <mtk:speed>%.6f</mtk:speed>%s',                   $record_speed,        $GPX_EOL) if (defined($record_speed));
 
1523
    print $fp sprintf('      <mtk:heading>%.6f</mtk:heading>%s',               $record_heading,      $GPX_EOL) if (defined($record_heading));
 
1524
    print $fp sprintf('      <mtk:satinview>%u</mtk:satinview>%s',             $record_nsat_in_view, $GPX_EOL) if (defined($record_nsat_in_view));
 
1525
 
 
1526
    if (defined($record_satdata)) {
 
1527
        my $sat;
 
1528
        foreach $sat (split(/\n/, $record_satdata)) {
 
1529
            my ($sid, $inuse, $elevation, $azimuth, $snr) = split(/\t/, $sat);
 
1530
            print $fp sprintf('      <mtk:satdata sid="%u" inuse="%u">%s',   $sid, $inuse, $GPX_EOL);
 
1531
            print $fp sprintf('        <mtk:elevation>%d</mtk:elevation>%s', $elevation,   $GPX_EOL) if ($elevation ne '');
 
1532
            print $fp sprintf('        <mtk:azimuth>%u</mtk:azimuth>%s',     $azimuth,     $GPX_EOL) if ($azimuth   ne '');
 
1533
            print $fp sprintf('        <mtk:snr>%u</mtk:snr>%s',             $snr,         $GPX_EOL) if ($snr       ne '');
 
1534
            print $fp sprintf('      </mtk:satdata>%s', $GPX_EOL);
 
1535
        }
 
1536
    }
 
1537
 
 
1538
    print $fp sprintf('      <mtk:msec>%u</mtk:msec>%s',           $record_millisecond,  $GPX_EOL) if (defined($record_millisecond));
 
1539
    print $fp sprintf('      <mtk:distance>%.9f</mtk:distance>%s', $record_distance,     $GPX_EOL) if (defined($record_distance));
 
1540
    print $fp sprintf('    </mtk:wptExtension>%s', $GPX_EOL);
 
1541
    print $fp sprintf('  </extensions>%s', $GPX_EOL);
 
1542
 
 
1543
    }
 
1544
}
 
1545
 
 
1546
#-------------------------------------------------------------------------
 
1547
# Log time.
 
1548
#-------------------------------------------------------------------------
 
1549
sub log_time {
 
1550
    time2str('%H:%M:%S', time());
 
1551
}
 
1552
 
 
1553
#-------------------------------------------------------------------------
 
1554
# Return the flash memory size upon model ID.
 
1555
#-------------------------------------------------------------------------
 
1556
sub flash_memory_size {
 
1557
    my $model = shift;
 
1558
    return( 8 * 1024 * 1024 / 8) if ($model == 0x1388); # 757/ZI v1        8 Mbit = 1 Mb
 
1559
    return( 8 * 1024 * 1024 / 8) if ($model == 0x5202); # 757/ZI v2        8 Mbit = 1 Mb
 
1560
    return(32 * 1024 * 1024 / 8) if ($model == 0x8300); # Qstartz BT-1200 32 Mbit = 4 Mb
 
1561
    # 0x0051    i-Blue 737, Qstartz 810, Polaris iBT-GPS, Holux M1000
 
1562
    # 0x0002    Qstartz 815
 
1563
    # 0x001b    i-Blue 747
 
1564
    # 0x001d    BT-Q1000 / BGL-32
 
1565
    # 0x0131    EB-85A
 
1566
    return(16 * 1024 * 1024 / 8); # 16Mbit -> 2Mb
 
1567
}
 
1568
 
 
1569
#-------------------------------------------------------------------------
 
1570
# Functions for serial communication. Use the global variable $device.
 
1571
#-------------------------------------------------------------------------
 
1572
# Open the serial port.
 
1573
sub serial_port_open {
 
1574
    my $port = shift;
 
1575
    die("Cannot open $port. Did you turn on the GPS device?\n") if (! -c $port);
 
1576
    $device = Device::SerialPort->new($port);
 
1577
    $device->baudrate(115200)   || die "fail setting parity";
 
1578
    $device->parity('none')     || die "fail setting parity";
 
1579
    $device->databits(8)        || die "fail setting databits";
 
1580
    $device->stopbits(1)        || die "fail setting stopbits";
 
1581
    $device->handshake('none')  || die "fail setting handshake";
 
1582
    $device->write_settings     || die "no settings";
 
1583
}
 
1584
 
 
1585
# Close the port.
 
1586
sub serial_port_close {
 
1587
    $device->close || warn "close failed";
 
1588
}
 
1589
 
 
1590
# Write the received string to the port.
 
1591
# Return the bytes actually written.
 
1592
sub serial_port_write {
 
1593
    return($device->write(shift));
 
1594
}
 
1595
 
 
1596
# Set read timeout (in milliseconds) on the port.
 
1597
sub serial_port_set_read_timeout {
 
1598
    $device->read_const_time(shift);
 
1599
}
 
1600
 
 
1601
# Get a character from the port.
 
1602
# Return the number of characters read (1 if success) and the character itself.
 
1603
sub serial_port_getch {
 
1604
    return($device->read(1));
 
1605
}