gpstool 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|