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.
@@ -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
@@ -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)
@@ -0,0 +1,4 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
@@ -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'
@@ -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")
@@ -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
@@ -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
@@ -0,0 +1,9 @@
1
+ module Gps #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -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)