3
# Copyright (C) 2007 Niccolo Rigacci
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.
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.
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.
19
# Author: Niccolo Rigacci <niccolo@rigacci.org>
21
# Version: 0.6 2008-02-07
23
# Control program for GPS units using the MediaTek (MTK) chipset.
24
# Tested to work with i-Blue 747 GPS data logger.
28
# Use the basename() function.
30
# Use the getopts() function.
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.
38
my $NAME = basename($0);
50
# Size in bytes of data types.
55
my $SIZEOF_DOUBLE = 8;
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;
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
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;
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;
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;
129
# Values for the RCR field.
131
my $RCR_SPEED = 0x02;
132
my $RCR_DISTANCE = 0x04;
133
my $RCR_BUTTON = 0x08;
135
# Recording method: OVERLAP or STOP.
136
my $RCD_METHOD_OVP = 1;
137
my $RCD_METHOD_STP = 2;
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;
145
# End Of Line for generate GPX files.
148
# Default timeout for packet wait (sec).
150
# Timeout for activity on device port (msec).
151
my $TIMEOUT_IDLE_PORT = 5000;
153
#-------------------------------------------------------------------------
155
#-------------------------------------------------------------------------
156
my $debug = $LOG_ERR; # Default loggin level.
157
my $port = '/dev/ttyUSB0'; # Default communication port.
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;
177
my $next_write_address;
178
my $expected_records_total;
187
my $non_written_sector_found;
193
my $record_longitude;
202
my $record_nsat_in_use;
203
my $record_nsat_in_view;
206
my $record_millisecond;
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);
216
Usage: $NAME [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:
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
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');
247
#-------------------------------------------------------------------------
248
# Do not open the device, read instead an existing binary log file.
249
#-------------------------------------------------------------------------
251
if ($opt_t or $opt_w) {
252
# Parse binary data and save GPX files.
254
# Total number of records is unknown: we will exit on End Of File.
255
$expected_records_total = 0xffffffff;
261
#-------------------------------------------------------------------------
262
# Initialize the device port.
263
#-------------------------------------------------------------------------
264
serial_port_open($port);
266
# Do not write the log file yet.
269
# Send test packet (PMTK_TEST).
270
packet_send('PMTK000');
271
packet_wait('PMTK001,0,');
272
print "MTK Test OK\n";
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]+)\*/) {
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]+)\*/) {
289
printf "MTK Firmware: Version: $version, Release: $release, Model ID: $model_id\n";
291
#-------------------------------------------------------------------------
293
#-------------------------------------------------------------------------
295
printf(">> Erasing log memory...\n");
296
packet_send('PMTK182,6,1');
297
packet_wait('PMTK001,182,6,3', 20);
300
#-------------------------------------------------------------------------
301
# Turn ON or OFF data logging.
302
#-------------------------------------------------------------------------
303
if ($opt_l eq 'on') {
304
printf(">> Switch recording to ON\n");
306
packet_send('PMTK182,4');
307
packet_wait('PMTK001,182,4,3');
309
if ($opt_l eq 'off') {
310
printf(">> Switch recording to OFF\n");
312
packet_send('PMTK182,5');
313
packet_wait('PMTK001,182,5,3');
316
#-------------------------------------------------------------------------
317
# Set recording criteria: TIME, DISTANCE, SPEED.
318
#-------------------------------------------------------------------------
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');
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');
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');
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');
350
printf(">> Setting method STOP on memory full\n");
351
packet_send('PMTK182,1,6,2');
353
$ret = packet_wait('PMTK001,182,1,');
354
if ($ret =~ m/PMTK001,182,1,(\d)/) {
356
printf(">> ERROR: Cannot set recording method\n");
361
#-------------------------------------------------------------------------
362
# Set log format (PMTK_LOG_SETFORMAT).
363
#-------------------------------------------------------------------------
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');
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);
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);
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);
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);
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]+)\*/) {
413
printf("Recording method on memory full: (%u) %s\n", $rec_method, describe_recording_method($rec_method));
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]+)\*/) {
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");
426
if ($log_status & $LOG_STATUS_DISABLE) {
427
printf("WARNING! Log status DISABLE_LOG, may too many failed sectors!\n");
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);
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);
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]+)\*/) {
457
printf("Memory health status (failed sectors mask): %s\n", $fail_sectors);
461
#-------------------------------------------------------------------------
462
# Get binary data from the device and save to a file.
463
#-------------------------------------------------------------------------
464
if ($opt_f and ($opt_t or $opt_w)) {
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);
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;
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: $!");
480
# Avoid reading the entire memory if we find a non-written sector.
481
$non_written_sector_found = 0;
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).
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);
499
# Parse binary data and save GPX files.
508
#-------------------------------------------------------------------------
509
# Calculate the packet checksum: bitwise XOR of string's bytes.
510
#-------------------------------------------------------------------------
511
sub packet_checksum {
514
my $len = length($pkt);
518
for ($i = 0; $i < $len; $i++) { $check ^= ord(substr($pkt, $i, 1)); }
519
#printf("0x%02X\n", $check);
523
#-------------------------------------------------------------------------
524
# Send NMEA packet to the device.
525
#-------------------------------------------------------------------------
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";
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));
542
#-------------------------------------------------------------------------
543
# Read a packet from the device.
544
# Return the packet with PktType, DataField, "*" and Checksum.
546
# Example: PMTK182,3,8,0004E69C*13
548
# The packet received has a leading Preample and a trailing <CR><LF>,
549
# example: $PMTK182,3,8,0004E69C*13<CR><LF>
550
#-------------------------------------------------------------------------
561
# Timeout (in milliseconds) for activity on the port.
562
$timeout = $TIMEOUT_IDLE_PORT if (!defined($timeout));
563
serial_port_set_read_timeout($timeout);
565
# Wait packet preamble.
568
($n, $c) = serial_port_getch();
569
die("ERROR: Reading from device: $!") if ($n != 1);
572
# Read until End Of Packet.
576
($n, $c) = serial_port_getch();
577
die("ERROR: Reading from device: $!") if ($n != 1);
583
if (($c eq "\n") and ($previous_c eq "\r")) {
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);
593
# Extract packet payload and checksum.
594
$payload = substr($pkt, 0, -3);
595
$checksum = hex(substr($pkt, -2, 2));
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);
606
#-------------------------------------------------------------------------
607
# Read packets from the device, untill we get the one we want.
608
#-------------------------------------------------------------------------
611
my $pkt_type = shift;
618
$len = length($pkt_type);
620
# Timeout (in seconds) for packet wait.
621
$timeout = $TIMEOUT if (!defined($timeout));
622
$max_time = time() + $timeout;
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);
630
printf("%s ERROR: packet_wait() failed for packet %s\n", log_time(), $pkt_type) if ($debug >= $LOG_ERR);
634
#-------------------------------------------------------------------------
635
# Append received packets of log data to a file.
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 {
652
# Save only datalog packets (PMTK_LOG_RESP_DATA).
653
return if (substr($pkt, 0, 10) ne 'PMTK182,8,');
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;
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;
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);
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);
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)));
681
$percent = ($log_offset / $bytes_to_read) * 100;
682
printf("Saved log data: %6.2f%%\n", $percent);
686
#-------------------------------------------------------------------------
687
# Parse raw binary log data and write GPX files.
688
#-------------------------------------------------------------------------
702
my $expected_records_sector = 0;
703
my $record_count_sector = 0;
704
my $record_count_total = 0;
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";
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;
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);
724
# Print current file position.
725
printf("Reading offset %08X\n", tell($fp)) if ($debug >= $LOG_INFO);
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;
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);
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);
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);
758
printf("Total record count: %u\n", $record_count_total);
764
#--------------------------------------------------------
766
# - record separator: AAAAAAAAAAAAAAXXYYYYYYYYBBBBBBBB
767
# - non written space: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
768
#--------------------------------------------------------
769
if (($log_len - tell($fp)) >= $SIZEOF_SEPARATOR) {
771
$buffer = my_read($fp, $SIZEOF_SEPARATOR);
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.
779
gpx_print_trk_end($fp_gpx_trk) if ($opt_t);
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);
789
next; # Search for the next record or record separator.
791
} elsif ($buffer eq (chr(0xff) x $SIZEOF_SEPARATOR)) {
792
#----------------------------------------------------------
793
# Found non-written space.
794
#----------------------------------------------------------
795
# Close the current <trk> in GPX.
797
gpx_print_trk_end($fp_gpx_trk) if ($opt_t);
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.
810
printf("ERROR: End of log file. Total number of read records: %u, expected %u\n", $record_count_total, $expected_records_total);
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);
820
# None of above, should be record data: rewind the file pointer so we can read it.
821
seek($fp, -$SIZEOF_SEPARATOR, 1);
825
#-----------------------------------------
827
#-----------------------------------------
828
$record_count_sector++;
829
$record_count_total++;
831
printf("Reading log sector: record %u (%u/%u total)\n", $record_count_sector, $record_count_total, $expected_records_total) if ($debug >= $LOG_INFO);
833
# Read each record field.
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
942
undef($record_satdata);
943
if ($log_format & $LOG_FORMAT_SID) {
944
my $satdata_count = 0;
948
my $satdata_elevation;
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);
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);
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);
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);
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;
996
last if ($satdata_count >= $satdata_inview);
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);
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);
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);
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));
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);
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);
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);
1049
gpx_print_trkpt($fp_gpx_trk) if ($opt_t);
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);
1060
# Eventually close the <trk> GPX tags.
1062
gpx_print_trk_end($fp_gpx_trk) if ($opt_t);
1066
# Write GPX tracks file.
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 $_; }
1074
gpx_print_gpx_end($fp_gpx);
1076
unlink($gpx_trk_tmp_fname);
1079
# Write GPX waypoints file.
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 $_; }
1087
gpx_print_gpx_end($fp_gpx);
1089
unlink($gpx_wpt_tmp_fname);
1093
#-------------------------------------------------------------------------
1094
# Read some bytes from the device and return them.
1095
#-------------------------------------------------------------------------
1100
my $n = read($handle, $variable, $length);
1101
printf("ERROR: Reading file: read %u bytes, expected %u\n", $n, $length) if ($n != $length);
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 {
1111
my $sector_header = shift;
1113
if ($debug >= $LOG_NOTICE) {
1115
printf("Sector header: %s\n", uc(unpack('H*', $sector_header))) if ($debug >= $LOG_DEBUG);
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);
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
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);
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)));
1154
return($count, $format);
1157
#-------------------------------------------------------------------------
1158
# Given a log format value (bitmask), return a description string.
1159
#-------------------------------------------------------------------------
1160
sub describe_log_format {
1162
my $log_format = shift;
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);
1186
return(substr($str, 1));
1189
#-------------------------------------------------------------------------
1191
#-------------------------------------------------------------------------
1192
sub encode_log_format {
1194
my $log_format = shift;
1195
my $changes = shift;
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/);
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/);
1239
return($log_format);
1242
#-------------------------------------------------------------------------
1243
# Given a log format value (bitmask), return the record size in bytes.
1244
#-------------------------------------------------------------------------
1245
sub sizeof_log_format {
1247
my $log_format = shift;
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);
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);
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);
1279
return($size_wpt, $size_sat);
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);
1297
#-------------------------------------------------------------------------
1298
# Return a string describing some field.
1299
#-------------------------------------------------------------------------
1300
sub describe_valid_mtk {
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);
1314
# Description suitable for GPX <trkpt> <fix> element.
1315
sub describe_valid_gpx {
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);
1324
sub describe_rcr_mtk {
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);
1333
return('Unknown') if ($str eq '');
1334
return(substr($str, 1));
1337
# Description suitable for GPX <trkpt> <type> element.
1338
sub describe_rcr_gpx {
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);
1347
return(undef) if ($str eq '');
1348
return(substr($str, 1));
1351
sub describe_log_status {
1352
my $log_status = shift;
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));
1363
sub describe_recording_method {
1365
return('OVERLAP') if ($val == $RCD_METHOD_OVP);
1366
return('STOP') if ($val == $RCD_METHOD_STP);
1370
#-------------------------------------------------------------------------
1371
# Silly function: return a byte!
1372
#-------------------------------------------------------------------------
1377
#-------------------------------------------------------------------------
1378
# Convert a 16 bit binary data into an integer.
1379
#-------------------------------------------------------------------------
1380
sub mtk2unsignedword {
1387
#-------------------------------------------------------------------------
1388
# Convert a 32 bit binary data into a long integer number.
1389
#-------------------------------------------------------------------------
1394
#-------------------------------------------------------------------------
1395
# Convert a 32 bit binary data into a float number.
1396
#-------------------------------------------------------------------------
1401
#-------------------------------------------------------------------------
1402
# Convert a 64 bit binary data into a double precision number.
1403
#-------------------------------------------------------------------------
1408
#-------------------------------------------------------------------------
1409
# Convert seconds from epoch (long int) to UTC timestamp.
1410
#-------------------------------------------------------------------------
1412
time2str('%Y-%m-%dT%H:%M:%SZ', shift, 'GMT');
1415
#-------------------------------------------------------------------------
1416
# Print the header of a GPX file.
1417
#-------------------------------------------------------------------------
1418
sub gpx_print_gpx_begin {
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);
1439
sub gpx_print_gpx_end {
1441
print $fp sprintf('</gpx>%s', $GPX_EOL);
1444
#-------------------------------------------------------------------------
1445
# Open and close the GPX <trk> tag.
1446
#-------------------------------------------------------------------------
1447
sub gpx_print_trk_begin {
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);
1455
sub gpx_print_trk_end {
1457
print $fp sprintf('</trkseg>%s', $GPX_EOL);
1458
print $fp sprintf('</trk>%s', $GPX_EOL);
1461
#-------------------------------------------------------------------------
1462
# Print a GPX <trkpt>.
1463
#-------------------------------------------------------------------------
1464
sub gpx_print_trkpt {
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);
1477
#-------------------------------------------------------------------------
1478
# Print a GPX <wpt>.
1479
#-------------------------------------------------------------------------
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);
1498
#-------------------------------------------------------------------------
1499
# Print <trkpt> and <wpt> common attributes.
1500
#-------------------------------------------------------------------------
1501
sub gpx_print_pt_attributes {
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));
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)) {
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));
1526
if (defined($record_satdata)) {
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);
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);
1546
#-------------------------------------------------------------------------
1548
#-------------------------------------------------------------------------
1550
time2str('%H:%M:%S', time());
1553
#-------------------------------------------------------------------------
1554
# Return the flash memory size upon model ID.
1555
#-------------------------------------------------------------------------
1556
sub flash_memory_size {
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
1564
# 0x001d BT-Q1000 / BGL-32
1566
return(16 * 1024 * 1024 / 8); # 16Mbit -> 2Mb
1569
#-------------------------------------------------------------------------
1570
# Functions for serial communication. Use the global variable $device.
1571
#-------------------------------------------------------------------------
1572
# Open the serial port.
1573
sub serial_port_open {
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";
1586
sub serial_port_close {
1587
$device->close || warn "close failed";
1590
# Write the received string to the port.
1591
# Return the bytes actually written.
1592
sub serial_port_write {
1593
return($device->write(shift));
1596
# Set read timeout (in milliseconds) on the port.
1597
sub serial_port_set_read_timeout {
1598
$device->read_const_time(shift);
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));