gps 0.0.1
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/History.txt +4 -0
- data/License.txt +510 -0
- data/Manifest.txt +24 -0
- data/README.txt +97 -0
- data/Rakefile +4 -0
- data/config/hoe.rb +67 -0
- data/config/requirements.rb +17 -0
- data/lib/gps.rb +21 -0
- data/lib/gps/fix.rb +13 -0
- data/lib/gps/receiver.rb +112 -0
- data/lib/gps/receivers/gpsd.rb +69 -0
- data/lib/gps/version.rb +9 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/setup.rb +1585 -0
- data/spec/fix_spec.rb +27 -0
- data/spec/gpsd_spec.rb +63 -0
- data/spec/receiver_spec.rb +111 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- data/tasks/deployment.rake +31 -0
- data/tasks/environment.rake +7 -0
- data/tasks/rspec.rake +21 -0
- data/tasks/website.rake +9 -0
- metadata +80 -0
data/Manifest.txt
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
History.txt
|
2
|
+
License.txt
|
3
|
+
Manifest.txt
|
4
|
+
README.txt
|
5
|
+
Rakefile
|
6
|
+
config/hoe.rb
|
7
|
+
config/requirements.rb
|
8
|
+
lib/gps.rb
|
9
|
+
lib/gps/fix.rb
|
10
|
+
lib/gps/receiver.rb
|
11
|
+
lib/gps/receivers/gpsd.rb
|
12
|
+
lib/gps/version.rb
|
13
|
+
script/destroy
|
14
|
+
script/generate
|
15
|
+
setup.rb
|
16
|
+
spec/fix_spec.rb
|
17
|
+
spec/gpsd_spec.rb
|
18
|
+
spec/receiver_spec.rb
|
19
|
+
spec/spec.opts
|
20
|
+
spec/spec_helper.rb
|
21
|
+
tasks/deployment.rake
|
22
|
+
tasks/environment.rake
|
23
|
+
tasks/rspec.rake
|
24
|
+
tasks/website.rake
|
data/README.txt
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
= GPS Library for Ruby
|
2
|
+
|
3
|
+
This library provides a Ruby interface to GPS receivers. Features include:
|
4
|
+
* Support for multiple receivers. +Receiver+ connects to a GPS and obtains all data in a +Fix+.
|
5
|
+
* GPSD +Receiver+ for obtaining data from units supported by GPSD.
|
6
|
+
* Event callbacks for position, speed and course change.
|
7
|
+
* Plugin architecture. Distribute new +Receiver+ implementations as gems.
|
8
|
+
|
9
|
+
== Installation
|
10
|
+
|
11
|
+
Simply install via gems with the command:
|
12
|
+
|
13
|
+
gem install gps
|
14
|
+
|
15
|
+
Or run the included setup.rb like so:
|
16
|
+
|
17
|
+
ruby setup.rb config
|
18
|
+
ruby setup.rb install
|
19
|
+
|
20
|
+
== Example usage
|
21
|
+
|
22
|
+
There are a few simple steps to using this library, but before doing any of them you must first:
|
23
|
+
|
24
|
+
require "gps"
|
25
|
+
|
26
|
+
=== Create the +Receiver+
|
27
|
+
|
28
|
+
A +Receiver+ is created by calling +Gps::Receiver.create+ with an implementation and hash of options. If no implementation is given, the first found implementation is used. In a stock installation without any additional plugins, the following calls are equivalent:
|
29
|
+
|
30
|
+
gps = Gps::Receiver.create
|
31
|
+
gps = Gps::Receiver.create("gpsd")
|
32
|
+
gps = Gps::Receiver.create(:host => "localhost", :port => 2947)
|
33
|
+
gps = Gps::Receiver.create("gpsd", :host => "localhost")
|
34
|
+
|
35
|
+
The GPSD +Receiver+ supports both _:host_ and _:port_ options which point to a host on which a GPSD instance is running. +Receiver+ options are not standard.
|
36
|
+
|
37
|
+
=== Start the +Receiver+
|
38
|
+
|
39
|
+
The +Receiver+ is now created, but is not yet polling the hardware. To start this process, run:
|
40
|
+
|
41
|
+
gps.start
|
42
|
+
|
43
|
+
Verify that this has succeeded by running:
|
44
|
+
|
45
|
+
gps.started?
|
46
|
+
|
47
|
+
Assuming this succeeded, you can now access _gps.latitude_, _gps.longitude_, _gps.altitude, _gps.course_, _gps.speed_ and other variables exposed via +Fix+.
|
48
|
+
|
49
|
+
=== Callbacks
|
50
|
+
|
51
|
+
You can also register callbacks which are triggered on various GPS events like so:
|
52
|
+
|
53
|
+
gps.on_position_change { puts "Latitude = #{gps.latitude}, longitude = #{gps.longitude}" }
|
54
|
+
gps.on_course_change { puts "Course = #{gps.course}" }
|
55
|
+
gps.on_speed_change { puts "Speed = #{gps.speed}" }
|
56
|
+
|
57
|
+
+Receiver#on_position_change+ supports an additional _threshold_. Because of GPS drift, positions change often. As such, it is possible to specify a threshold like so:
|
58
|
+
|
59
|
+
gps.on_position_change(0.0002) { puts "Latitude = #{gps.latitude}, longitude = #{gps.longitude}" }
|
60
|
+
|
61
|
+
The callback will not trigger on position changes of less than 0.0002 degrees since it was last called.
|
62
|
+
|
63
|
+
=== Cleaning up
|
64
|
+
|
65
|
+
To close devices or connections and no longer receive GPS updates, call the following method:
|
66
|
+
|
67
|
+
gps.stop
|
68
|
+
|
69
|
+
== Hacking
|
70
|
+
|
71
|
+
The GPSD +Receiver+ should be sufficient for most needs. If, however, you wish to parse NMEA data directly, interface with raw serial ports or interact with your GPS hardware on a lower level, a new +Receiver+ implementation is necessary. This section is a quick overview to writing +Receiver+ implementations.
|
72
|
+
|
73
|
+
When overriding the below methods, +super+ should be the last method called. Also, as polling happens in a separate thread, +Receiver+ code should be thread-safe.
|
74
|
+
|
75
|
+
=== Use +Gps::Receivers+
|
76
|
+
|
77
|
+
The library looks up +Receiver+ implementations in the +Gps::Receivers+ module. Therefore, all implementations must be defined there.
|
78
|
+
|
79
|
+
=== Override +Receiver#start+
|
80
|
+
|
81
|
+
This method should open the device, initiate a connection, etc.
|
82
|
+
|
83
|
+
=== Override +Receiver#stop+, if Necessary
|
84
|
+
|
85
|
+
The default +stop+ method simply kills the thread and sets it to _nil_. If this is sufficient, overriding is unnecessary.
|
86
|
+
|
87
|
+
=== Override +Receiver#update+
|
88
|
+
|
89
|
+
This method should read an update from the device, setting the variables exposed by +Fix+.
|
90
|
+
|
91
|
+
=== Make it a Plugin
|
92
|
+
|
93
|
+
If the gem_plugin library is installed, new +Receivers+ distributed as gems can be integrated automatically with a few simple steps.
|
94
|
+
1. Distribute your +Receiver+ as a gem that depends on both the gps and gem_plugin gems.
|
95
|
+
2. Add the following code to your plugin:
|
96
|
+
|
97
|
+
GemPlugin::Manager.instance.create("/receivers/your_receiver_name", :optional_option => :value)
|
data/Rakefile
ADDED
data/config/hoe.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'gps/version'
|
2
|
+
|
3
|
+
AUTHOR = "Nolan Darilek" # can also be an array of Authors
|
4
|
+
EMAIL = "nolan@thewordnerd.info"
|
5
|
+
DESCRIPTION = "An interface to GPS receivers"
|
6
|
+
GEM_NAME = "gps"
|
7
|
+
RUBYFORGE_PROJECT = "hermes" # The unix name for your project
|
8
|
+
HOMEPATH = "http://hermes-gps.info/"
|
9
|
+
DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
|
10
|
+
|
11
|
+
@config_file = "~/.rubyforge/user-config.yml"
|
12
|
+
@config = nil
|
13
|
+
RUBYFORGE_USERNAME = "unknown"
|
14
|
+
def rubyforge_username
|
15
|
+
unless @config
|
16
|
+
begin
|
17
|
+
@config = YAML.load(File.read(File.expand_path(@config_file)))
|
18
|
+
rescue
|
19
|
+
puts <<-EOS
|
20
|
+
ERROR: No rubyforge config file found: #{@config_file}"
|
21
|
+
Run 'rubyforge setup' to prepare your env for access to Rubyforge
|
22
|
+
- See http://newgem.rubyforge.org/rubyforge.html for more details
|
23
|
+
EOS
|
24
|
+
exit
|
25
|
+
end
|
26
|
+
end
|
27
|
+
RUBYFORGE_USERNAME.replace @config["username"]
|
28
|
+
end
|
29
|
+
|
30
|
+
REV = ENV["SNAPSHOT"]? ".#{`bzr revno`.chomp}": ""
|
31
|
+
VERS = Gps::VERSION::STRING+REV
|
32
|
+
|
33
|
+
RDOC_OPTS = ['--quiet', '--title', 'gps documentation',
|
34
|
+
"--opname", "index.html",
|
35
|
+
"--line-numbers",
|
36
|
+
"--main", "README",
|
37
|
+
"--inline-source"]
|
38
|
+
|
39
|
+
class Hoe
|
40
|
+
def extra_deps
|
41
|
+
@extra_deps.reject! { |x| Array(x).first == 'hoe' }
|
42
|
+
@extra_deps
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Generate all the Rake tasks
|
47
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
48
|
+
hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
49
|
+
p.author = AUTHOR
|
50
|
+
p.description = DESCRIPTION
|
51
|
+
p.email = EMAIL
|
52
|
+
p.summary = "This library provides an elegant interface to data from GPS receivers. An extensible architecture simplifies adding new receiver types, with a GPSD receiver already written. Additionally, the API supports registering callbacks triggered on change of position, course, altitude and visible satellites."
|
53
|
+
p.url = HOMEPATH
|
54
|
+
p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
|
55
|
+
p.test_globs = ["test/**/test_*.rb"]
|
56
|
+
p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
|
57
|
+
|
58
|
+
# == Optional
|
59
|
+
p.changes = p.paragraphs_of("History.txt", 0..1).join("\\n\\n")
|
60
|
+
#p.extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
|
61
|
+
|
62
|
+
#p.spec_extras = {} # A hash of extra values to set in the gemspec.
|
63
|
+
end
|
64
|
+
|
65
|
+
CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
|
66
|
+
PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
|
67
|
+
hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
include FileUtils
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
%w[rake hoe newgem rubigen].each do |req_gem|
|
6
|
+
begin
|
7
|
+
require req_gem
|
8
|
+
rescue LoadError
|
9
|
+
puts "This Rakefile requires the '#{req_gem}' RubyGem."
|
10
|
+
puts "Installation: gem install #{req_gem} -y"
|
11
|
+
exit
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
$:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
|
16
|
+
|
17
|
+
require 'gps'
|
data/lib/gps.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
module Gps
|
4
|
+
end
|
5
|
+
|
6
|
+
begin
|
7
|
+
require "rubygems"
|
8
|
+
rescue LoadError
|
9
|
+
end
|
10
|
+
|
11
|
+
if Object.const_defined?("Gem")
|
12
|
+
begin
|
13
|
+
require "gem_plugin"
|
14
|
+
rescue LoadError
|
15
|
+
end
|
16
|
+
end
|
17
|
+
require "gps/fix"
|
18
|
+
require "gps/receiver"
|
19
|
+
require "gps/receivers/gpsd"
|
20
|
+
|
21
|
+
GemPlugin::Manager.instance.load "gps" => GemPlugin::INCLUDE if Object.const_defined?("GemPlugin")
|
data/lib/gps/fix.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Module representing all data in a GPS fix.
|
2
|
+
module Gps::Fix
|
3
|
+
attr_reader :last_tag, :timestamp, :timestamp_error_estimate, :latitude, :longitude, :altitude, :horizontal_error_estimate, :vertical_error_estimate, :course, :speed, :climb, :course_error_estimate, :speed_error_estimate, :climb_error_estimate, :satellites
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
@altitude = 0
|
7
|
+
@latitude = 0
|
8
|
+
@longitude = 0
|
9
|
+
@speed = 0
|
10
|
+
@course = 0
|
11
|
+
@satellites = 0
|
12
|
+
end
|
13
|
+
end
|
data/lib/gps/receiver.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
# Represents a provider of GPS fixes and a dispatcher of event callbacks for applications needing
|
2
|
+
# such fixes.
|
3
|
+
module Gps
|
4
|
+
class Receiver
|
5
|
+
include Fix
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
super
|
9
|
+
@last_latitude = @last_longitude = 0
|
10
|
+
@position_change_threshold = 0
|
11
|
+
@last_speed = 0
|
12
|
+
@last_course = 0
|
13
|
+
@last_altitude = 0
|
14
|
+
@last_satellites = 0
|
15
|
+
end
|
16
|
+
|
17
|
+
# Factory for creating a +Receiver+.
|
18
|
+
# Accepts an implementation and a hash of options, both optional. If no
|
19
|
+
# implementation is provided, the first is used by default. Implementations are in
|
20
|
+
# the module +Gps::Receivers+.
|
21
|
+
def self.create(arg = nil, args = {})
|
22
|
+
implementation = arg.respond_to?(:capitalize)? Receivers.const_get(arg.capitalize): Receivers.const_get(Receivers.constants[0])
|
23
|
+
options = arg.kind_of?(Hash)? arg: args
|
24
|
+
implementation.new(options)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Sets the position from an array of the form [latitude, longitude], calling the
|
28
|
+
# _on_position_change_ callback if necessary.
|
29
|
+
def position=(value)
|
30
|
+
@latitude = value[0]
|
31
|
+
@longitude = value[1]
|
32
|
+
call_position_change_if_necessary
|
33
|
+
end
|
34
|
+
|
35
|
+
# Called on position change, but only if the change is greater than +threshold+ degrees.
|
36
|
+
# The block receives the amount of change in degrees.
|
37
|
+
def on_position_change(threshold = 0, &block)
|
38
|
+
@position_change_threshold = threshold
|
39
|
+
@on_position_change = block
|
40
|
+
end
|
41
|
+
|
42
|
+
# Called on speed change.
|
43
|
+
def on_speed_change(&block)
|
44
|
+
@on_speed_change = block
|
45
|
+
end
|
46
|
+
|
47
|
+
# Called on course change.
|
48
|
+
def on_course_change(&block)
|
49
|
+
@on_course_change = block
|
50
|
+
end
|
51
|
+
|
52
|
+
# Called when the altitude changes.
|
53
|
+
def on_altitude_change(&block)
|
54
|
+
@on_altitude_change = block
|
55
|
+
end
|
56
|
+
|
57
|
+
# Called when the number of visible satellites changes.
|
58
|
+
def on_satellites_change(&block)
|
59
|
+
@on_satellites_change = block
|
60
|
+
end
|
61
|
+
|
62
|
+
# Override this in children. Opens the connection, device, etc.
|
63
|
+
def start
|
64
|
+
@thread = Thread.new do
|
65
|
+
while true
|
66
|
+
update
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Override this in children. Closes the device, connection, etc. and stops updating.
|
72
|
+
def stop
|
73
|
+
@thread.kill if @thread
|
74
|
+
@thread = nil
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns _true_ if started, _false_ otherwise.
|
78
|
+
def started?
|
79
|
+
!@thread.nil? && @thread.alive?
|
80
|
+
end
|
81
|
+
|
82
|
+
# Override this in children, setting fix variables from the GPS receiver.
|
83
|
+
# Here we dispatch to callbacks if necessary.
|
84
|
+
def update
|
85
|
+
call_position_change_if_necessary
|
86
|
+
@on_speed_change.call if @on_speed_change and @speed != @last_speed
|
87
|
+
@last_speed = @speed
|
88
|
+
@on_course_change.call if @on_course_change and @course != @last_course
|
89
|
+
@last_course = @course
|
90
|
+
@on_altitude_change.call if @on_altitude_change and @altitude != @last_altitude
|
91
|
+
@last_altitude = @altitude
|
92
|
+
@on_satellites_change.call if @on_satellites_change and @satellites != @last_satellites
|
93
|
+
@last_satellites = @satellites
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
def call_position_change_if_necessary
|
98
|
+
if position_change > @position_change_threshold
|
99
|
+
@on_position_change.call(position_change) if @on_position_change
|
100
|
+
@last_latitude = @latitude
|
101
|
+
@last_longitude = @longitude
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def position_change
|
106
|
+
(@latitude-@last_latitude).abs+(@longitude-@last_longitude).abs
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
module Receivers
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "socket"
|
2
|
+
|
3
|
+
# Represents a +Receiver+ that obtains information from GPSD.
|
4
|
+
module Gps::Receivers
|
5
|
+
class Gpsd < Gps::Receiver
|
6
|
+
attr_reader :host, :port
|
7
|
+
|
8
|
+
# Accepts an options +Hash+ consisting of the following:
|
9
|
+
# * _:host_: The host to which to connect
|
10
|
+
# * _:port_: The port to which to connect
|
11
|
+
def initialize(options = {})
|
12
|
+
super
|
13
|
+
@host ||= options[:host] ||= "localhost"
|
14
|
+
@port = options[:port] ||= 2947
|
15
|
+
end
|
16
|
+
|
17
|
+
def start
|
18
|
+
@socket = TCPSocket.new(@host, @port)
|
19
|
+
@socket.puts("w+")
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
def update
|
24
|
+
line = @socket.gets.chomp.split(",")[1]
|
25
|
+
return if !line
|
26
|
+
types = []
|
27
|
+
data = []
|
28
|
+
line.split(",").each do |sentence|
|
29
|
+
sentence.split("=").each_with_index do |d, i|
|
30
|
+
if i%2 == 0
|
31
|
+
types << d
|
32
|
+
else
|
33
|
+
data << d
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
types.each_with_index do |type, i|
|
38
|
+
begin
|
39
|
+
send("parse_#{type.downcase}", data[i])
|
40
|
+
rescue NoMethodError
|
41
|
+
end
|
42
|
+
end
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def parse_o(data)
|
48
|
+
params = data.split
|
49
|
+
@last_tag = params[0]
|
50
|
+
@timestamp = params[1].to_f
|
51
|
+
@timestamp_error_estimate = params[2].to_f
|
52
|
+
@latitude = params[3].to_f
|
53
|
+
@longitude = params[4].to_f
|
54
|
+
@altitude = params[5].to_f
|
55
|
+
@horizontal_error_estimate = params[6].to_f
|
56
|
+
@vertical_error_estimate = params[7].to_f
|
57
|
+
@course = params[8].to_f
|
58
|
+
@speed = params[9].to_f
|
59
|
+
@climb = params[10].to_f
|
60
|
+
@course_error_estimate = params[11].to_f
|
61
|
+
@speed_error_estimate = params[12].to_f
|
62
|
+
@climb_error_estimate = params[13].to_f
|
63
|
+
end
|
64
|
+
|
65
|
+
def parse_y(data)
|
66
|
+
@satellites = data.split(":")[0].split[-1].to_i
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/gps/version.rb
ADDED
data/script/destroy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/destroy'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Destroy.new.run(ARGV)
|