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.
@@ -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
@@ -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,3 @@
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.
@@ -0,0 +1,3 @@
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.
@@ -0,0 +1,3 @@
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.
@@ -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
+