gpstool 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+