gpstool 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +17 -0
- data/bin/gps-import-rbt-2300 +269 -0
- data/lib/gpstool.rb +11 -0
- data/lib/gpstool/device.rb +61 -0
- data/lib/gpstool/devices/rbt-2300.rb +100 -0
- data/lib/gpstool/devices/rbt-2300/data-buckets.rb +74 -0
- data/lib/gpstool/devices/rbt-2300/data-parser.rb +55 -0
- data/lib/gpstool/extensions/debug_io.rb +45 -0
- data/lib/gpstool/extensions/io.rb +21 -0
- data/lib/gpstool/extensions/numeric.rb +9 -0
- data/lib/gpstool/filters/distance.rb +35 -0
- data/lib/gpstool/filters/velocity.rb +35 -0
- data/lib/gpstool/formats/csv.rb +3 -0
- data/lib/gpstool/formats/gpx.rb +3 -0
- data/lib/gpstool/formats/kml.rb +3 -0
- data/lib/gpstool/message.rb +145 -0
- data/lib/gpstool/version.rb +13 -0
- data/lib/gpstool/world-point.rb +47 -0
- metadata +85 -0
data/README.rdoc
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
= GPSTool
|
2
|
+
|
3
|
+
Author:: Samuel Williams (http://www.oriontransfer.co.nz)
|
4
|
+
Copyright:: Copyright (c) 2011 Samuel Williams
|
5
|
+
License:: MIT
|
6
|
+
|
7
|
+
GPSTool provides infrastructure to read data from physical GPS devices or files, filter and output data in a variety of different formats. As part of this it also provides a variety of classes for dealing with GPS data
|
8
|
+
|
9
|
+
== Device Support
|
10
|
+
|
11
|
+
* RBT-2300
|
12
|
+
|
13
|
+
== File Format Support
|
14
|
+
|
15
|
+
* CSV
|
16
|
+
* GPX
|
17
|
+
* KML
|
@@ -0,0 +1,269 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
require 'gpstool'
|
6
|
+
require 'gpstool/devices/rbt-2300'
|
7
|
+
|
8
|
+
RBT = GPSTool::Devices::RBT2300
|
9
|
+
|
10
|
+
$stdout.sync = true
|
11
|
+
|
12
|
+
def progress(s, f, p)
|
13
|
+
return unless OPTIONS[:ShowProgress]
|
14
|
+
f = 1.0 if f > 1.0
|
15
|
+
puts "#{(s.to_f + (f.to_f * p.to_f)).to_i}%"
|
16
|
+
end
|
17
|
+
|
18
|
+
R2D = 180.0 / Math::PI
|
19
|
+
|
20
|
+
require 'optparse'
|
21
|
+
|
22
|
+
OPTIONS = {
|
23
|
+
:DevicePath => nil,
|
24
|
+
:OneLog => false,
|
25
|
+
:DestinationPath => "./",
|
26
|
+
:Erase => false,
|
27
|
+
:ShowProgress => false
|
28
|
+
}
|
29
|
+
|
30
|
+
ARGV.options do |o|
|
31
|
+
script_name = File.basename($0)
|
32
|
+
|
33
|
+
o.set_summary_indent(' ')
|
34
|
+
o.banner = "Usage: #{script_name} [options]"
|
35
|
+
o.define_head "This script is used to download data from RBT-2300 via Bluetooth."
|
36
|
+
|
37
|
+
o.on("-d /dev/path", "Set the device path.") { |path| OPTIONS[:DevicePath] = path }
|
38
|
+
o.on("-o /path/", "Set the output directory.") { |path| OPTIONS[:DestinationPath] = path }
|
39
|
+
o.on("-c", "Combine all output into one log.") { OPTIONS[:OneLog] = true }
|
40
|
+
|
41
|
+
o.on("--progress", "Show percentage progress to stdout.") { OPTIONS[:ShowProgress] = true }
|
42
|
+
|
43
|
+
o.on("-e", "Erase device. Don't do anything else.") { OPTIONS[:Erase] = true }
|
44
|
+
|
45
|
+
o
|
46
|
+
end.parse!
|
47
|
+
|
48
|
+
puts "Opening device..."
|
49
|
+
progress(5, 0, 0)
|
50
|
+
|
51
|
+
if OPTIONS[:DevicePath] == nil or !File.exists?(OPTIONS[:DevicePath])
|
52
|
+
puts "Cannot open file device: #{OPTIONS[:DevicePath] || 'unspecified'}"
|
53
|
+
exit 1
|
54
|
+
end
|
55
|
+
|
56
|
+
begin
|
57
|
+
$dev = GPSTool::Device.open(OPTIONS[:DevicePath], :message_class => RBT::Message)
|
58
|
+
rescue
|
59
|
+
STDERR.puts "Error! Could not open device: #{$!}"
|
60
|
+
exit 5
|
61
|
+
end
|
62
|
+
|
63
|
+
STTY = "/bin/stty"
|
64
|
+
#system(STTY, "-f", OPTIONS[:DevicePath], "230400");
|
65
|
+
system(STTY, "-f", OPTIONS[:DevicePath], "57600")
|
66
|
+
#system(STTY, "-a", "-f", OPTIONS[:DevicePath])
|
67
|
+
|
68
|
+
if (OPTIONS[:Erase])
|
69
|
+
$dev.send_message("PROY109,-1")
|
70
|
+
sleep 5
|
71
|
+
exit 0
|
72
|
+
end
|
73
|
+
|
74
|
+
$dev.send_message("PROY103,1,0")
|
75
|
+
$dev.send_message("PROY108")
|
76
|
+
|
77
|
+
$files = 0
|
78
|
+
|
79
|
+
$dev.read_message do |msg|
|
80
|
+
if msg.name == "LOG108"
|
81
|
+
$files = msg[:files].to_i
|
82
|
+
puts "#{msg[:files]} log files available..."
|
83
|
+
false
|
84
|
+
else
|
85
|
+
true
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
$logs = []
|
90
|
+
|
91
|
+
(0...$files).each do |a|
|
92
|
+
progress(5, a.to_f / $files.to_f, 5)
|
93
|
+
|
94
|
+
$dev.send_message("PROY101,#{a}")
|
95
|
+
|
96
|
+
$dev.read_message do |msg|
|
97
|
+
if msg.name == "LOG101"
|
98
|
+
$logs << [a, msg]
|
99
|
+
false
|
100
|
+
else
|
101
|
+
true
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
$total = 0
|
107
|
+
$count = 0
|
108
|
+
|
109
|
+
$logs.each do |idx,log|
|
110
|
+
date = RBT::parse_date(log)
|
111
|
+
puts "#{idx}: (#{date.to_s}) #{log[:frames]} frames logged in memory (@#{log[:pointer]})"
|
112
|
+
$total += log[:frames].to_i
|
113
|
+
end
|
114
|
+
|
115
|
+
$retrieved = []
|
116
|
+
|
117
|
+
start_time = Time.now
|
118
|
+
max = 20
|
119
|
+
|
120
|
+
$logs.each do |idx,log|
|
121
|
+
#log_file = "rbt-#{log[:date]}-#{idx}.csv"
|
122
|
+
|
123
|
+
#if File.exists? log_file
|
124
|
+
# puts "File #{log_file} already exists - skipping data download"
|
125
|
+
# next
|
126
|
+
#end
|
127
|
+
|
128
|
+
$dev.send_message("PROY101,#{idx}")
|
129
|
+
|
130
|
+
frame_sz = RBT::frame_size(log[:mode].to_i)
|
131
|
+
frames_pp = RBT::frames_per_packet(log[:mode].to_i)
|
132
|
+
|
133
|
+
data_buckets = RBT::DataBuckets.new(log[:frames].to_i, frame_sz)
|
134
|
+
|
135
|
+
start = log[:pointer].to_i
|
136
|
+
|
137
|
+
# puts "Frame size: #{frame_sz} bytes"
|
138
|
+
|
139
|
+
while true
|
140
|
+
fetch_start = data_buckets.first_missing_bucket
|
141
|
+
break if fetch_start == nil
|
142
|
+
|
143
|
+
fetch_cnt = data_buckets.missing_buckets(fetch_start)
|
144
|
+
fetch_cnt = fetch_cnt > max ? max : fetch_cnt
|
145
|
+
|
146
|
+
mem_offset = start + fetch_start * frame_sz
|
147
|
+
|
148
|
+
puts "Fetching #{fetch_cnt} frames starting from #{fetch_start} (@#{mem_offset})..."
|
149
|
+
|
150
|
+
# $dev.clear_io
|
151
|
+
|
152
|
+
$dev.send_message("PROY102,#{mem_offset},#{log[:mode]},#{fetch_cnt}")
|
153
|
+
|
154
|
+
packets_cnt = RBT::estimate_packet_count(frame_sz, fetch_cnt)
|
155
|
+
packets = []
|
156
|
+
|
157
|
+
begin
|
158
|
+
$dev.read_message do |msg|
|
159
|
+
if msg.name == "LOG102" && msg[:data] != nil
|
160
|
+
# We got a useful message
|
161
|
+
header, data = RBT::read_log_data(msg[:data])
|
162
|
+
|
163
|
+
if header[2] != nil and data != nil
|
164
|
+
if packets.include? header[0]
|
165
|
+
STDERR.puts "*** WARNING, DATA IS MOST LIKELY CORRUPT, PLEASE DISCARD RESULTS AND RE-RUN!!!"
|
166
|
+
exit 1
|
167
|
+
end
|
168
|
+
|
169
|
+
packets << header[0]
|
170
|
+
puts "Packets = #{packets.inspect}"
|
171
|
+
|
172
|
+
data_offset = fetch_start + (header[0] * frames_pp)
|
173
|
+
|
174
|
+
puts "Received #{frames_pp} frames at offset @#{data_offset}..."
|
175
|
+
|
176
|
+
$count += data_buckets.append_data(data_offset, data)
|
177
|
+
progress(10, $count.to_f / $total.to_f, 90)
|
178
|
+
end
|
179
|
+
|
180
|
+
# puts "Headers: #{packets.inspect} - #{packets.size} / #{packets_cnt}"
|
181
|
+
packets.size < packets_cnt and packets[-1] != (packets_cnt-1)
|
182
|
+
else
|
183
|
+
true
|
184
|
+
end
|
185
|
+
end
|
186
|
+
rescue GPSTool::InvalidChecksumError
|
187
|
+
$stderr.puts $!
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
puts "Fetched all frames for log #{log[:date]}."
|
192
|
+
|
193
|
+
# puts data_buckets.data.inspect
|
194
|
+
# puts data_buckets.buckets.inspect
|
195
|
+
|
196
|
+
# idx = Dir["dump-*.raw"].size
|
197
|
+
# dump_name = "dump-#{idx.to_s.rjust(3, '0')}.raw"
|
198
|
+
# f = File.open(dump_name, "w")
|
199
|
+
# f.write(data_buckets.data)
|
200
|
+
# f.close
|
201
|
+
|
202
|
+
$retrieved << [log, data_buckets.data]
|
203
|
+
end
|
204
|
+
|
205
|
+
EXT = ".unicsv"
|
206
|
+
def create_unicsv(log_name, ext = EXT)
|
207
|
+
i = 0
|
208
|
+
path = nil
|
209
|
+
begin
|
210
|
+
path = File.join(OPTIONS[:DestinationPath], log_name + (i == 0 ? "" : "-#{i}") + ext)
|
211
|
+
i += 1
|
212
|
+
end while File.exists?(path)
|
213
|
+
|
214
|
+
puts "FILE #{path}"
|
215
|
+
|
216
|
+
output = File.open(path, "w")
|
217
|
+
output.puts ["lat", "lon", "alt", "speed", "date", "time"].join(",")
|
218
|
+
|
219
|
+
return output
|
220
|
+
end
|
221
|
+
|
222
|
+
$count = 0
|
223
|
+
|
224
|
+
if $retrieved.size > 0
|
225
|
+
output = nil
|
226
|
+
|
227
|
+
if OPTIONS[:OneLog] == true
|
228
|
+
start_date = RBT::parse_date($retrieved.first[0])
|
229
|
+
end_date = RBT::parse_date($retrieved.last[0])
|
230
|
+
log_name = "rbt-combined-#{start_date}-#{end_date}"
|
231
|
+
output = create_unicsv(log_name)
|
232
|
+
end
|
233
|
+
|
234
|
+
$retrieved.each do |log, data|
|
235
|
+
date = RBT::parse_date(log)
|
236
|
+
|
237
|
+
log_name = "rbt-#{date}" unless OPTIONS[:OneLog]
|
238
|
+
|
239
|
+
puts "Writing log #{log[:date]} to CSV #{log_name}..."
|
240
|
+
|
241
|
+
parser = RBT::DataParser.new(log[:mode])
|
242
|
+
parser.append_data(data)
|
243
|
+
|
244
|
+
unless OPTIONS[:OneLog]
|
245
|
+
output = create_unicsv(log_name)
|
246
|
+
end
|
247
|
+
|
248
|
+
parser.frames.each do |f|
|
249
|
+
alt = f[1].size >= 3 ? f[1][2] : nil
|
250
|
+
spd = f[1].size >= 4 ? f[1][3] : nil
|
251
|
+
|
252
|
+
if (f[0][1] >= 24 or f[0][2] >= 60 or f[0][3] >= 60)
|
253
|
+
STDERR.puts "Corruption detected in data... please retry download!"
|
254
|
+
exit 2
|
255
|
+
end
|
256
|
+
|
257
|
+
$count += 1
|
258
|
+
output.puts [f[1][0]*R2D, f[1][1]*R2D, alt, spd, "#{date.year}/#{date.month}/#{date.day}", "#{f[0][1]}:#{f[0][2]}:#{f[0][3]}"].join(",")
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
output.close unless OPTIONS[:OneLog]
|
263
|
+
end
|
264
|
+
|
265
|
+
end_time = Time.now
|
266
|
+
|
267
|
+
puts "#{sprintf('%.3f', end_time - start_time)} seconds to download #{$count} records. #{$dev.errors} error(s)."
|
268
|
+
|
269
|
+
$dev.close
|
data/lib/gpstool.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# This file is part of the "GPSTool" project, and is distributed under the MIT License.
|
2
|
+
# Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
|
3
|
+
# See <LICENSE.txt> for licensing details.
|
4
|
+
|
5
|
+
require 'gpstool/world-point'
|
6
|
+
require 'gpstool/message'
|
7
|
+
|
8
|
+
module GPSTool
|
9
|
+
|
10
|
+
end
|
11
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# This file is part of the "GPSTool" project, and is distributed under the MIT License.
|
2
|
+
# Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
|
3
|
+
# See <LICENSE.txt> for licensing details.
|
4
|
+
|
5
|
+
module GPSTool
|
6
|
+
class Device
|
7
|
+
def self.open(path, options = {})
|
8
|
+
return self.new(File.open(path, "rb+"), options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(io, options = {})
|
12
|
+
@io = io
|
13
|
+
@errors = 0
|
14
|
+
|
15
|
+
@options = options
|
16
|
+
end
|
17
|
+
|
18
|
+
attr :io
|
19
|
+
attr :errors
|
20
|
+
|
21
|
+
def close
|
22
|
+
@io.close
|
23
|
+
end
|
24
|
+
|
25
|
+
def clear_io
|
26
|
+
# Consume any pre-existing data
|
27
|
+
pipes = nil
|
28
|
+
while (pipes = IO.select([@io], [], [], 0.1))
|
29
|
+
puts pipes.inspect
|
30
|
+
|
31
|
+
buf = @io.read_nonblock(1024)
|
32
|
+
$stderr.puts "(clear) -> " + buf.dump
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def send_message(message)
|
37
|
+
unless Message === message
|
38
|
+
message = Message.parse(message)
|
39
|
+
end
|
40
|
+
|
41
|
+
$stderr.puts "(write) <- " + message.to_s.dump
|
42
|
+
@io.write(message.to_s + "\r\n")
|
43
|
+
end
|
44
|
+
|
45
|
+
def read_message(&block)
|
46
|
+
message_class = @options[:message_class] || Message
|
47
|
+
|
48
|
+
while true
|
49
|
+
begin
|
50
|
+
message = message_class.read(@io, @options)
|
51
|
+
|
52
|
+
return unless yield message
|
53
|
+
rescue
|
54
|
+
@errors += 1
|
55
|
+
|
56
|
+
raise
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# This file is part of the "GPSTool" project, and is distributed under the MIT License.
|
2
|
+
# Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
|
3
|
+
# See <LICENSE.txt> for licensing details.
|
4
|
+
|
5
|
+
require 'gpstool/device'
|
6
|
+
require 'gpstool/devices/rbt-2300/data-buckets'
|
7
|
+
require 'gpstool/devices/rbt-2300/data-parser'
|
8
|
+
|
9
|
+
require 'date'
|
10
|
+
|
11
|
+
module GPSTool
|
12
|
+
module Devices
|
13
|
+
module RBT2300
|
14
|
+
class Message < ::GPSTool::Message
|
15
|
+
@@messages = {
|
16
|
+
"LOG101" => [:date, :mode, :frames, :pointer],
|
17
|
+
"LOG102" => [:data],
|
18
|
+
"LOG108" => [:mode, nil, nil, :overwrite, nil, :rate, nil, :files, nil]
|
19
|
+
}
|
20
|
+
|
21
|
+
def self.read_body(io, name, options)
|
22
|
+
if (name == "LOG102")
|
23
|
+
buffer = '$' + name
|
24
|
+
buffer += io.readbytes(1)
|
25
|
+
|
26
|
+
header_data = io.readbytes(3)
|
27
|
+
buffer += header_data
|
28
|
+
|
29
|
+
if header_data[0,2] == "0*"
|
30
|
+
# This is sometimes a response when
|
31
|
+
# an invalid segment is requested
|
32
|
+
buffer += io.gets
|
33
|
+
|
34
|
+
self.validate(buffer)
|
35
|
+
|
36
|
+
return self.new([name, nil])
|
37
|
+
else
|
38
|
+
header = header_data.unpack("CCC")
|
39
|
+
|
40
|
+
# Frame count is in bytes
|
41
|
+
data_size = header[2]
|
42
|
+
data = io.readbytes(data_size)
|
43
|
+
|
44
|
+
buffer += data
|
45
|
+
buffer += io.gets
|
46
|
+
|
47
|
+
# Check that the data was correct
|
48
|
+
self.validate(buffer)
|
49
|
+
|
50
|
+
return self.new([name, header_data + data])
|
51
|
+
end
|
52
|
+
else
|
53
|
+
return super(io, name, options)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
MAX_PACKET_SIZE = 0xF0
|
59
|
+
|
60
|
+
def self.parse_date(log)
|
61
|
+
log[:date].match(/([0-9]{4})([0-9]{2})([0-9]{2})/)
|
62
|
+
date = Date.civil($1.to_i, $2.to_i, $3.to_i)
|
63
|
+
|
64
|
+
return date
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.update_config(update_time, mode)
|
68
|
+
update_time = update_time.to_i
|
69
|
+
|
70
|
+
if update_time < 1 or update_time > 60
|
71
|
+
raise ArgumentError.new("Update time must be between 1-60 inclusive.")
|
72
|
+
end
|
73
|
+
|
74
|
+
mode = mode.to_i
|
75
|
+
|
76
|
+
if mode < 0 || mode > 3
|
77
|
+
raise ArgumentError.new("Mode must be one of 0, 1, 2.")
|
78
|
+
end
|
79
|
+
|
80
|
+
a = 0
|
81
|
+
b = update_time.to_i
|
82
|
+
c = mode
|
83
|
+
d = 0
|
84
|
+
|
85
|
+
return Message.parse("PROY104,#{a},#{b},#{c},#{d}")
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.read_log_data(data)
|
89
|
+
header = data[0..3].unpack("CCC")
|
90
|
+
data = data[3..-1]
|
91
|
+
|
92
|
+
return header, data
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.estimate_packet_count(frame_sz, count)
|
96
|
+
((frame_sz * count).to_f / MAX_PACKET_SIZE).ceil.to_i
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# This file is part of the "GPSTool" project, and is distributed under the MIT License.
|
2
|
+
# Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
|
3
|
+
# See <LICENSE.txt> for licensing details.
|
4
|
+
|
5
|
+
module GPSTool
|
6
|
+
module Devices
|
7
|
+
module RBT2300
|
8
|
+
|
9
|
+
class DataBuckets
|
10
|
+
def initialize(expected_frames, frame_size)
|
11
|
+
@expected_frames = expected_frames
|
12
|
+
@frame_size = frame_size
|
13
|
+
|
14
|
+
@data = "\0" * (@expected_frames * @frame_size)
|
15
|
+
@buckets = [nil] * @expected_frames
|
16
|
+
|
17
|
+
@progress = 0
|
18
|
+
end
|
19
|
+
|
20
|
+
attr :data
|
21
|
+
attr :buckets
|
22
|
+
attr :progress
|
23
|
+
|
24
|
+
def append_data(frame_offset, data)
|
25
|
+
frames_read = data.size / @frame_size
|
26
|
+
@progress += frames_read
|
27
|
+
|
28
|
+
@buckets[frame_offset] = frames_read
|
29
|
+
|
30
|
+
mem_offset = frame_offset * @frame_size
|
31
|
+
@data[mem_offset,data.size] = data
|
32
|
+
|
33
|
+
return frames_read
|
34
|
+
end
|
35
|
+
|
36
|
+
def first_missing_bucket
|
37
|
+
k = 0
|
38
|
+
|
39
|
+
while k < @buckets.size
|
40
|
+
c = @buckets[k]
|
41
|
+
|
42
|
+
if c == nil
|
43
|
+
return k
|
44
|
+
elsif c > 0
|
45
|
+
k += c
|
46
|
+
else
|
47
|
+
# $stderr.puts "Error finding missing bucket: #{@buckets.inspect}, #{k}, #{c}"
|
48
|
+
k += 1
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if k == @buckets.size
|
53
|
+
return nil
|
54
|
+
else
|
55
|
+
return k
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def missing_buckets(start)
|
60
|
+
return nil if start == nil
|
61
|
+
|
62
|
+
k = start
|
63
|
+
|
64
|
+
while k < @buckets.size and @buckets[k] == nil
|
65
|
+
k += 1
|
66
|
+
end
|
67
|
+
|
68
|
+
return k - start
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# This file is part of the "GPSTool" project, and is distributed under the MIT License.
|
2
|
+
# Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
|
3
|
+
# See <LICENSE.txt> for licensing details.
|
4
|
+
|
5
|
+
module GPSTool
|
6
|
+
module Devices
|
7
|
+
module RBT2300
|
8
|
+
def self.floats_for_format(format)
|
9
|
+
return 2 + format.to_i
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.frame_size(format)
|
13
|
+
# HEADER(4 bytes) + (sizeof(float) * count)
|
14
|
+
return 4 + (floats_for_format(format) * 4)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.frames_per_packet(format)
|
18
|
+
return 240 / frame_size(format)
|
19
|
+
end
|
20
|
+
|
21
|
+
class DataParser
|
22
|
+
def initialize(format = 0)
|
23
|
+
@frames = []
|
24
|
+
@floats = RBT2300::floats_for_format(format)
|
25
|
+
end
|
26
|
+
|
27
|
+
attr :frames
|
28
|
+
|
29
|
+
def append_data(datas)
|
30
|
+
sz = 4 + (@floats * 4)
|
31
|
+
|
32
|
+
while datas.size >= sz
|
33
|
+
# Pop buf off the front
|
34
|
+
buf = datas[0...sz]
|
35
|
+
datas = datas[sz..-1]
|
36
|
+
|
37
|
+
#puts "Inspecting data: #{buf.inspect}"
|
38
|
+
|
39
|
+
# Extract Date
|
40
|
+
date = buf.unpack("CCCC")
|
41
|
+
#puts "Extracted date: #{date.inspect}"
|
42
|
+
bufo = buf[4..-1]
|
43
|
+
|
44
|
+
# We need to extract IEEE floats
|
45
|
+
buf = bufo.unpack("C*").reverse.pack("C*")
|
46
|
+
numbers = buf.unpack("g" * @floats).reverse
|
47
|
+
|
48
|
+
@frames << [date, numbers]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# This file is part of the "GPSTool" project, and is distributed under the MIT License.
|
2
|
+
# Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
|
3
|
+
# See <LICENSE.txt> for licensing details.
|
4
|
+
|
5
|
+
class DebugIO
|
6
|
+
def initialize(io)
|
7
|
+
@io = io
|
8
|
+
end
|
9
|
+
|
10
|
+
def readbytes(count)
|
11
|
+
buffer = @io.readbytes(count)
|
12
|
+
|
13
|
+
$stderr.puts "(readbytes) -> " + buffer.dump
|
14
|
+
|
15
|
+
return buffer
|
16
|
+
end
|
17
|
+
|
18
|
+
def read(count)
|
19
|
+
buffer = @io.read(count)
|
20
|
+
|
21
|
+
$stderr.puts "(read) -> " + buffer.dump
|
22
|
+
|
23
|
+
return buffer
|
24
|
+
end
|
25
|
+
|
26
|
+
def gets(eol = "\r\n")
|
27
|
+
buffer = @io.gets(eol)
|
28
|
+
|
29
|
+
$stderr.puts "(gets) -> " + buffer.dump
|
30
|
+
|
31
|
+
return buffer
|
32
|
+
end
|
33
|
+
|
34
|
+
def getc
|
35
|
+
char = @io.getc
|
36
|
+
|
37
|
+
$stderr.puts "(getc) -> " + char.chr
|
38
|
+
|
39
|
+
return char
|
40
|
+
end
|
41
|
+
|
42
|
+
def ungetc(c)
|
43
|
+
@io.ungetc(c)
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# This file is part of the "GPSTool" project, and is distributed under the MIT License.
|
2
|
+
# Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
|
3
|
+
# See <LICENSE.txt> for licensing details.
|
4
|
+
|
5
|
+
class IO
|
6
|
+
def readbytes(length)
|
7
|
+
buffer = ""
|
8
|
+
|
9
|
+
while buffer.size < length
|
10
|
+
begin
|
11
|
+
buffer += read(length - buffer.size)
|
12
|
+
$stderr.puts "(*readbytes) -> #{buffer.dump}"
|
13
|
+
rescue Errno::EAGAIN
|
14
|
+
IO.select([self], [], [])
|
15
|
+
retry
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
return buffer
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# This file is part of the "GPSTool" project, and is distributed under the MIT License.
|
2
|
+
# Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
|
3
|
+
# See <LICENSE.txt> for licensing details.
|
4
|
+
|
5
|
+
class Numeric
|
6
|
+
def to_radians
|
7
|
+
self.to_f / 180.0 * Math::PI
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# This file is part of the "GPSTool" project, and is distributed under the MIT License.
|
2
|
+
# Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
|
3
|
+
# See <LICENSE.txt> for licensing details.
|
4
|
+
|
5
|
+
module GPSTool
|
6
|
+
module Filters
|
7
|
+
module Distance
|
8
|
+
def self.filter_points(points, options)
|
9
|
+
filtered_points = []
|
10
|
+
i = 0
|
11
|
+
|
12
|
+
while i < points.size
|
13
|
+
current = points[i]
|
14
|
+
|
15
|
+
filtered_points << current
|
16
|
+
|
17
|
+
j = i + 1
|
18
|
+
|
19
|
+
while j < points.size
|
20
|
+
step = points[j]
|
21
|
+
distance = current.distanceTo(step)
|
22
|
+
|
23
|
+
break if distance < options[:distance]
|
24
|
+
|
25
|
+
j += 1
|
26
|
+
end
|
27
|
+
|
28
|
+
i = j
|
29
|
+
end
|
30
|
+
|
31
|
+
return filtered_points
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# This file is part of the "GPSTool" project, and is distributed under the MIT License.
|
2
|
+
# Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
|
3
|
+
# See <LICENSE.txt> for licensing details.
|
4
|
+
|
5
|
+
module GPSTool
|
6
|
+
module Filters
|
7
|
+
module Velocity
|
8
|
+
def self.filter_points(points, options)
|
9
|
+
filtered_points = []
|
10
|
+
i = 0
|
11
|
+
|
12
|
+
while i < points.size
|
13
|
+
current = points[i]
|
14
|
+
|
15
|
+
filtered_points << current
|
16
|
+
|
17
|
+
j = i + 1
|
18
|
+
|
19
|
+
while j < points.size
|
20
|
+
step = points[j]
|
21
|
+
speed = current.averageSpeedTo(step)
|
22
|
+
|
23
|
+
break if speed < options[:speed]
|
24
|
+
|
25
|
+
j += 1
|
26
|
+
end
|
27
|
+
|
28
|
+
i = j
|
29
|
+
end
|
30
|
+
|
31
|
+
return filtered_points
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# This file is part of the "GPSTool" project, and is distributed under the MIT License.
|
2
|
+
# Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
|
3
|
+
# See <LICENSE.txt> for licensing details.
|
4
|
+
|
5
|
+
require 'gpstool/extensions/io'
|
6
|
+
|
7
|
+
module GPSTool
|
8
|
+
class InvalidChecksumError < StandardError
|
9
|
+
def initialize(data, sum)
|
10
|
+
@data = data
|
11
|
+
@sum = sum
|
12
|
+
end
|
13
|
+
|
14
|
+
attr :data
|
15
|
+
attr :sum
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"Invalid Checksum: #{data.dump}*#{sum}, should be #{Message.checksum_hex(data)}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Message
|
23
|
+
@@messages = {}
|
24
|
+
|
25
|
+
def self.checksum(data)
|
26
|
+
sum = 0
|
27
|
+
|
28
|
+
data.size.times do |i|
|
29
|
+
sum = sum ^ data[i]
|
30
|
+
end
|
31
|
+
|
32
|
+
return sum
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.checksum_hex(body)
|
36
|
+
sprintf("%X", checksum(body).to_i).rjust(2, "0")
|
37
|
+
end
|
38
|
+
|
39
|
+
# This function performs validation and normalisation on the input data.
|
40
|
+
def self.validate(data)
|
41
|
+
data.strip!
|
42
|
+
|
43
|
+
data = data[1..-1] if data[0,1] == "$"
|
44
|
+
sum = nil
|
45
|
+
|
46
|
+
if data[-3,1] == "*"
|
47
|
+
sum = data[-2,2]
|
48
|
+
data = data[0..-4]
|
49
|
+
|
50
|
+
if checksum_hex(data) != sum
|
51
|
+
raise InvalidChecksumError.new(data, sum)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
return data
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.parse(data)
|
59
|
+
data = self.validate(data)
|
60
|
+
|
61
|
+
return self.new(data)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.read_body(io, name, options)
|
65
|
+
return self.parse('$' + name + io.gets)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.read(io, options = {})
|
69
|
+
# Try to synchronise to the start message boundary
|
70
|
+
while io.readbytes(1) != '$'
|
71
|
+
end
|
72
|
+
|
73
|
+
name = ''
|
74
|
+
while true
|
75
|
+
c = io.readbytes(1)
|
76
|
+
|
77
|
+
if c == ',' || c == '*'
|
78
|
+
io.ungetc(c[0])
|
79
|
+
return self.read_body(io, name, options)
|
80
|
+
end
|
81
|
+
|
82
|
+
name += c
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def initialize(body)
|
87
|
+
if Array === body
|
88
|
+
@name = body.shift
|
89
|
+
@parts = body
|
90
|
+
else
|
91
|
+
@name, @parts = body.split(",", 2)
|
92
|
+
|
93
|
+
if @parts
|
94
|
+
if structure == nil
|
95
|
+
@parts = @parts.split(",")
|
96
|
+
elsif structure.size > 1
|
97
|
+
@parts = @parts.split(",", structure.size)
|
98
|
+
else
|
99
|
+
@parts = [@parts]
|
100
|
+
end
|
101
|
+
else
|
102
|
+
@parts = []
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
attr :parts
|
108
|
+
attr :name
|
109
|
+
|
110
|
+
def structure
|
111
|
+
messages = self.class.class_eval('@@messages')
|
112
|
+
messages[@name]
|
113
|
+
end
|
114
|
+
|
115
|
+
def to_hash
|
116
|
+
result = {}
|
117
|
+
structure.each_with_index do |n,i|
|
118
|
+
result[n] = @parts[i] if n
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def [](key)
|
123
|
+
if structure
|
124
|
+
offset = structure.index(key)
|
125
|
+
return @parts[offset]
|
126
|
+
end
|
127
|
+
|
128
|
+
return nil
|
129
|
+
end
|
130
|
+
|
131
|
+
def to_s
|
132
|
+
body = nil
|
133
|
+
|
134
|
+
if @parts.length > 0
|
135
|
+
body = "#{@name},#{@parts.join(',')}"
|
136
|
+
else
|
137
|
+
body = @name
|
138
|
+
end
|
139
|
+
|
140
|
+
sum = self.class.checksum_hex(body)
|
141
|
+
|
142
|
+
return "$#{body}*#{sum}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# This file is part of the "GPSTool" project, and is distributed under the MIT License.
|
2
|
+
# Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
|
3
|
+
# See <LICENSE.txt> for licensing details.
|
4
|
+
|
5
|
+
module GPSTool
|
6
|
+
module VERSION #:nodoc:
|
7
|
+
MAJOR = 0
|
8
|
+
MINOR = 1
|
9
|
+
TINY = 0
|
10
|
+
|
11
|
+
STRING = [MAJOR, MINOR, TINY].join('.')
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# This file is part of the "GPSTool" project, and is distributed under the MIT License.
|
2
|
+
# Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
|
3
|
+
# See <LICENSE.txt> for licensing details.
|
4
|
+
|
5
|
+
require 'gpstool/extensions/numeric'
|
6
|
+
|
7
|
+
module GPSTool
|
8
|
+
class WorldPoint
|
9
|
+
EARTH_RADIUS = 6371
|
10
|
+
|
11
|
+
def initialize(datetime, latitude, longitude, altitude = EARTH_RADIUS)
|
12
|
+
@datetime = datetime
|
13
|
+
@latitude = latitude
|
14
|
+
@longitude = longitude
|
15
|
+
@altitude = altitude
|
16
|
+
end
|
17
|
+
|
18
|
+
attr :latitude
|
19
|
+
attr :longitude
|
20
|
+
attr :altitude
|
21
|
+
attr :datetime
|
22
|
+
|
23
|
+
def distanceTo(other)
|
24
|
+
dLat = (other.latitude - self.latitude).to_radians
|
25
|
+
dLon = (other.longitude - self.longitude).to_radians
|
26
|
+
|
27
|
+
a = Math.sin(dLat/2) * Math.sin(dLat/2) +
|
28
|
+
Math.cos(self.latitude.to_radians) * Math.cos(other.latitude.to_radians) *
|
29
|
+
Math.sin(dLon/2) * Math.sin(dLon/2)
|
30
|
+
|
31
|
+
c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))
|
32
|
+
|
33
|
+
d = EARTH_RADIUS * c
|
34
|
+
end
|
35
|
+
|
36
|
+
def averageSpeedTo(other)
|
37
|
+
# In Km
|
38
|
+
distance = self.distanceTo(other)
|
39
|
+
|
40
|
+
# In hours
|
41
|
+
duration = (other.datetime - @datetime) * 24
|
42
|
+
|
43
|
+
# In Km/hr
|
44
|
+
return distance / duration
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gpstool
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Samuel Williams
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-05-24 00:00:00 +12:00
|
19
|
+
default_executable: gps-import-rbt-2300
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description:
|
23
|
+
email:
|
24
|
+
executables:
|
25
|
+
- gps-import-rbt-2300
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- README.rdoc
|
30
|
+
files:
|
31
|
+
- lib/gpstool/device.rb
|
32
|
+
- lib/gpstool/devices/rbt-2300/data-buckets.rb
|
33
|
+
- lib/gpstool/devices/rbt-2300/data-parser.rb
|
34
|
+
- lib/gpstool/devices/rbt-2300.rb
|
35
|
+
- lib/gpstool/extensions/debug_io.rb
|
36
|
+
- lib/gpstool/extensions/io.rb
|
37
|
+
- lib/gpstool/extensions/numeric.rb
|
38
|
+
- lib/gpstool/filters/distance.rb
|
39
|
+
- lib/gpstool/filters/velocity.rb
|
40
|
+
- lib/gpstool/formats/csv.rb
|
41
|
+
- lib/gpstool/formats/gpx.rb
|
42
|
+
- lib/gpstool/formats/kml.rb
|
43
|
+
- lib/gpstool/message.rb
|
44
|
+
- lib/gpstool/version.rb
|
45
|
+
- lib/gpstool/world-point.rb
|
46
|
+
- lib/gpstool.rb
|
47
|
+
- bin/gps-import-rbt-2300
|
48
|
+
- README.rdoc
|
49
|
+
has_rdoc: true
|
50
|
+
homepage: http://www.oriontransfer.co.nz/projects/GPSTool
|
51
|
+
licenses: []
|
52
|
+
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options:
|
55
|
+
- --main
|
56
|
+
- README.rdoc
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
hash: 3
|
65
|
+
segments:
|
66
|
+
- 0
|
67
|
+
version: "0"
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 3
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
requirements: []
|
78
|
+
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 1.6.2
|
81
|
+
signing_key:
|
82
|
+
specification_version: 3
|
83
|
+
summary: A framework for processing GPS data.
|
84
|
+
test_files: []
|
85
|
+
|