nmea_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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fa3fef3d96dff44cb620276abc67d7e7eab832f0
4
+ data.tar.gz: c5ed23427e8805430ce9392035a149f8d1c74f3e
5
+ SHA512:
6
+ metadata.gz: 40adfabfbbfcf6167565709b84634dfb3c8d0cffcd82bbe9f9a00b3b557df3016aed4064639259deef1864dafd206a3ad6208d8cbee616fbff9c2ab52e9272df
7
+ data.tar.gz: 29ddd56de23382fd20364a607b234efa347ae430ab1e6b515411ec00cdffb869965d5ed6811110f7e308287197426c0333bc48d546b42772fb4b2780f24fd708
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in nmea_gps.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,18 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ # Note: The cmd option is now required due to the increasing number of ways
5
+ # rspec may be run, below are examples of the most common uses.
6
+ # * bundler: 'bundle exec rspec'
7
+ # * bundler binstubs: 'bin/rspec'
8
+ # * spring: 'bin/rsspec' (This will use spring if running and you have
9
+ # installed the spring binstubs per the docs)
10
+ # * zeus: 'zeus rspec' (requires the server to be started separetly)
11
+ # * 'just' rspec: 'rspec'
12
+ guard :rspec, cmd: 'bundle exec rspec' do
13
+ watch(%r{^spec/.+_spec\.rb$})
14
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
15
+ watch('spec/spec_helper.rb') { "spec" }
16
+
17
+ end
18
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 ore
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,80 @@
1
+ ## Installation
2
+
3
+ Add this line to your application's Gemfile:
4
+
5
+ gem 'nmea_gps'
6
+
7
+ And then execute:
8
+
9
+ $ bundle
10
+
11
+ Or install it yourself as:
12
+
13
+ $ gem install nmea_gps
14
+
15
+ ## Usage
16
+
17
+ ```ruby
18
+
19
+ # create a serialport to your GPS receiver
20
+ # you should know how to open a connection between your GPS device
21
+ # mine runs on `9600 baudrate`, `8bit`, `1stop bit`, `none parity`
22
+ serialport = SerialPort.new("/dev/cu.your_whatever_device", 9600, 8, 1, SerialPort::NONE)
23
+
24
+ # you should know about your GPS receiver's update Hz(how often it will update logs 10Hz = 10 times per sec.)
25
+ # if you don't know leave it blank. It will at least update every sec.
26
+ gps = Nmea::Gps.new serialport update_hz: 10
27
+
28
+ # this will be called automatically!!
29
+ gps.rmc do |rmc|
30
+ p rmc.time
31
+ p rmc.latitude
32
+ p rmc.longitude
33
+ p rmc.heading
34
+ p rmc.date
35
+ end
36
+
37
+ # start the tracking
38
+ gps.track!
39
+
40
+ # you can also have
41
+ # gps.gga do |gga|
42
+ # ..
43
+ # end
44
+ # gps.gll do |gll|
45
+ # ..
46
+ # end
47
+ # gps.gsa do |gsa|
48
+ # ..
49
+ # end
50
+ # gps.vtg do |vtg|
51
+ # ..
52
+ # end
53
+ # gps.zda do |zda|
54
+ # ..
55
+ # end
56
+ #
57
+ # this one gets multiple Gsv objects
58
+ # gps.gsv do |gsvs|
59
+ # p gsvs.count
60
+ # ..
61
+ # end
62
+
63
+ # do your other works
64
+ # ..
65
+ # ..
66
+ # ..
67
+
68
+ # when you want to stop callbacks, you can call this
69
+ # gps.stop!
70
+ # then close the connection
71
+ # serialport.close
72
+ ```
73
+
74
+ ## Contributing
75
+
76
+ 1. Fork it ( https://github.com/[my-github-username]/nmea_gps/fork )
77
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
78
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
79
+ 4. Push to the branch (`git push origin my-new-feature`)
80
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
@@ -0,0 +1,11 @@
1
+ module Nmea
2
+ class Config
3
+ class << self
4
+ attr_accessor :time_zone
5
+
6
+ def time_zone_list
7
+ TZInfo::Timezone.all_identifiers
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,82 @@
1
+ module Nmea
2
+
3
+ class Gps
4
+ TALKER_ID = "GP"
5
+
6
+ def initialize(serial_port, update_hz: 1)
7
+ self.callbacks = {}
8
+ self.gps_serial_port = serial_port
9
+ self.update_hz = 1.second.to_f / update_hz.to_i
10
+ end
11
+
12
+ Dir[Pathname(__FILE__).join("../sentences/*.rb")].collect do |path|
13
+ Pathname(path).basename(".rb").to_s
14
+ end.each do |sentence|
15
+ define_method(sentence) do |&block|
16
+ self.callbacks[__callee__] = block
17
+ end
18
+ end
19
+
20
+ def track!
21
+ self.track_thread = Thread.new do
22
+ loop do
23
+ callback!
24
+ frequency
25
+ end
26
+ end
27
+ end
28
+
29
+ def stop!
30
+ self.track_thread.kill if self.track_thread.respond_to? :kill
31
+ end
32
+
33
+ protected
34
+ attr_accessor :gps_serial_port, :update_hz, :callbacks, :track_thread
35
+
36
+ private
37
+
38
+ def hz
39
+ self.update_hz
40
+ end
41
+
42
+ def frequency
43
+ sleep hz
44
+ end
45
+
46
+ def line_set
47
+ Hash.new{|hash, key| hash[key] = [] }.tap do |set|
48
+ loop do
49
+ line = self.gps_serial_port.gets("\r\n").strip
50
+ # %w[ GLL RMC VTG GGA GSA GSV ZDA].join.chars.uniq.sort.join
51
+ return unless match = line.match(/\A\$#{TALKER_ID}([ACDGLMRSTVZ]{3})/)
52
+
53
+ sentence = match[1].downcase.to_sym
54
+ set[sentence] << line
55
+ break if sentence == :gga
56
+ end
57
+ end
58
+ end
59
+
60
+ def callback!
61
+ this_set = line_set
62
+ this_set.keys.each do |sentence|
63
+ this_callback = self.callbacks[sentence]
64
+ next unless this_callback
65
+
66
+ target_class = "Nmea::Gps::#{sentence.to_s.camelcase}".constantize
67
+
68
+ object = if sentence == :gsv
69
+ this_set[sentence].sort.collect do |line|
70
+ target_class.new line
71
+ end
72
+ else
73
+ target_class.new(this_set[sentence].first)
74
+ end
75
+
76
+ this_callback.call object
77
+ end
78
+ end
79
+
80
+
81
+ end
82
+ end
@@ -0,0 +1,37 @@
1
+ module Nmea
2
+ class Gps
3
+
4
+ class SentenceBase
5
+
6
+ STATUSES = {
7
+ "A" => :valid,
8
+ "V" => :invalid,
9
+ }
10
+
11
+ MODES = {
12
+ "A" => :autonomous,
13
+ "N" => :no_fix,
14
+ "D" => :dgps,
15
+ "E" => :dr,
16
+ }
17
+
18
+
19
+ def initialize(line)
20
+ @line = line
21
+ end
22
+
23
+ def name
24
+ raise "override this method"
25
+ end
26
+
27
+ def raw_data
28
+ _, *data = self.line.split("*").first.split(",")
29
+ data
30
+ end
31
+
32
+ protected
33
+ attr_reader :line
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,70 @@
1
+ module Nmea
2
+ class Gps
3
+
4
+ class Gga < SentenceBase
5
+ include Nmea::UtcTimeable, Nmea::DddFormatable
6
+
7
+ QUALITIES = {
8
+ 0 => :invalid,
9
+ 1 => :gps_sps_fix,
10
+ 2 => :dgps_fix,
11
+ 3 => :gps_pps_fix,
12
+ }
13
+
14
+ def name
15
+ "Global positioning system fixed data"
16
+ end
17
+
18
+ def time
19
+ hhmmss_to_local_time raw_data[0]
20
+ end
21
+
22
+ def latitude
23
+ dmm_to_ddd raw_data[1], raw_data[2]
24
+ end
25
+
26
+ def longitude
27
+ dmm_to_ddd raw_data[3], raw_data[4]
28
+ end
29
+
30
+ def quality
31
+ QUALITIES[raw_data[5].to_i]
32
+ end
33
+
34
+ def number_of_satellites
35
+ raw_data[6].to_i
36
+ end
37
+ alias :satellites :number_of_satellites
38
+
39
+ # http://en.wikipedia.org/wiki/Dilution_of_precision_%28GPS%29
40
+ def horizontal_dilution_of_precision
41
+ raw_data[7].to_f
42
+ end
43
+ alias :hdop :horizontal_dilution_of_precision
44
+
45
+ def altitude_in_meters
46
+ raw_data[8].to_f
47
+ end
48
+ alias :altitude :altitude_in_meters
49
+
50
+ def height_of_geoid_above_wgs84_ellipsoid
51
+ raw_data[10].to_f
52
+ end
53
+ alias :height_of_geoid :height_of_geoid_above_wgs84_ellipsoid
54
+
55
+ def time_in_seconds_since_last_dgps_update
56
+ raw_data[12].to_i
57
+ end
58
+ alias :last_dgps_update :time_in_seconds_since_last_dgps_update
59
+
60
+ def dgps_station_id
61
+ raw_data[13].to_s
62
+ end
63
+
64
+ def checksum
65
+ raw_data[14].to_s
66
+ end
67
+
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,34 @@
1
+ module Nmea
2
+ class Gps
3
+
4
+ class Gll < SentenceBase
5
+ include Nmea::UtcTimeable, Nmea::DddFormatable
6
+
7
+ def name
8
+ "Geographic position—latitude/longitude"
9
+ end
10
+
11
+ def latitude
12
+ dmm_to_ddd raw_data[0], raw_data[1]
13
+ end
14
+
15
+ def longitude
16
+ dmm_to_ddd raw_data[2], raw_data[3]
17
+ end
18
+
19
+ def time
20
+ hhmmss_to_local_time raw_data[4]
21
+ end
22
+
23
+ def status
24
+ STATUSES[raw_data[5]]
25
+ end
26
+
27
+ def mode
28
+ MODES[raw_data[6]]
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+
@@ -0,0 +1,57 @@
1
+ module Nmea
2
+ class Gps
3
+
4
+ class Gsa < SentenceBase
5
+
6
+ MODE_SELECTIONS = {
7
+ "M" => :manual,
8
+ "A" => :automatic,
9
+ }
10
+
11
+ MODES = {
12
+ "1" => :fix_not_available,
13
+ "2" => :fix_2D,
14
+ "3" => :fix_3D,
15
+ }
16
+
17
+ def name
18
+ "GNSS DOP and active satellites"
19
+ end
20
+
21
+ def mode_selection
22
+ MODE_SELECTIONS[raw_data[0]]
23
+ end
24
+
25
+ def mode
26
+ MODES[raw_data[1]]
27
+ end
28
+
29
+ def ids_of_svs_used_in_position_fix(at = :all)
30
+ ids = raw_data[2..13]
31
+ return ids if at == :all
32
+
33
+ id = ids[at.to_i - 1]
34
+ return nil if id.blank?
35
+
36
+ id.to_i
37
+ end
38
+ alias :svs_ids :ids_of_svs_used_in_position_fix
39
+
40
+ def position_dilution_of_precision
41
+ raw_data[14].to_f
42
+ end
43
+ alias :pdop :position_dilution_of_precision
44
+
45
+ def horizontal_dilution_of_precision
46
+ raw_data[15].to_f
47
+ end
48
+ alias :hdop :horizontal_dilution_of_precision
49
+
50
+ def vertical_dilution_of_precision
51
+ raw_data[16].to_f
52
+ end
53
+ alias :vdop :vertical_dilution_of_precision
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,34 @@
1
+ module Nmea
2
+ class Gps
3
+
4
+ class Gsv < SentenceBase
5
+ def name
6
+ "GNSS satellites in view"
7
+ end
8
+
9
+ def number_of_message
10
+ raw_data[0].to_i
11
+ end
12
+
13
+ def message_number
14
+ raw_data[1].to_i
15
+ end
16
+
17
+ def number_of_satellites_in_view
18
+ raw_data[2].to_i
19
+ end
20
+
21
+ def satellites
22
+ raw_data[3..-1].each_slice(4).collect do |slice|
23
+ OpenStruct.new.tap do |satellite|
24
+ satellite.id = slice.first.to_i
25
+ satellite.elevation = slice[1].to_i
26
+ satellite.azinmuth = slice[2].to_i
27
+ satellite.signal_to_noise_ratio = satellite.snr = slice.last.to_i
28
+ end
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,65 @@
1
+ module Nmea
2
+ class Gps
3
+
4
+ class Rmc < SentenceBase
5
+ include Nmea::UtcTimeable, Nmea::DddFormatable
6
+
7
+ def name
8
+ "Recommended minimum specific GNSS data"
9
+ end
10
+
11
+ def time
12
+ hhmmss_to_local_time raw_data[0]
13
+ end
14
+
15
+ def status
16
+ STATUSES[raw_data[1]]
17
+ end
18
+
19
+ def latitude
20
+ dmm_to_ddd raw_data[2], raw_data[3]
21
+ end
22
+
23
+ def longitude
24
+ dmm_to_ddd raw_data[4], raw_data[5]
25
+ end
26
+
27
+ def knot_per_hour
28
+ raw_data[6].to_f
29
+ end
30
+
31
+ def km_per_hour
32
+ knot_per_hour * 1.85200
33
+ end
34
+
35
+ def mile_per_hour
36
+ knot_per_hour * 1.15077945
37
+ end
38
+
39
+ def heading
40
+ raw_data[7].to_f
41
+ end
42
+
43
+ def date
44
+ _, day, month, year = raw_data[8].match(/(\d{2})(\d{2})(\d{2})/).to_a
45
+ Date.parse("#{year}/#{month}/#{day}")
46
+ end
47
+
48
+ def magnetic_variation
49
+ return nil if raw_data[9].blank?
50
+ raw_data[9].to_f
51
+ end
52
+
53
+ def magnetic_variation_direction
54
+ return nil if raw_data[10].blank?
55
+ raw_data[10].to_f
56
+ end
57
+
58
+ def mode
59
+ MODES[raw_data[11]]
60
+ end
61
+
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,27 @@
1
+ module Nmea
2
+ class Gps
3
+
4
+ class Vtg < SentenceBase
5
+ def name
6
+ "Course over ground and ground speed"
7
+ end
8
+
9
+ def true_course
10
+ raw_data[0].to_f
11
+ end
12
+
13
+ def magnetic_course
14
+ return if raw_data[2].blank?
15
+ raw_data[2].to_f
16
+ end
17
+
18
+ def knot_per_hour
19
+ raw_data[4].to_f
20
+ end
21
+
22
+ def km_per_hour
23
+ raw_data[6].to_f
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,31 @@
1
+ module Nmea
2
+ class Gps
3
+
4
+ class Zda < SentenceBase
5
+ include Nmea::UtcTimeable
6
+
7
+ def name
8
+ "Date and Time"
9
+ end
10
+
11
+ def time
12
+ hhmmss_to_local_time raw_data[0]
13
+ end
14
+
15
+ def date
16
+ Date.parse("#{raw_data[3]}/#{raw_data[2]}/#{raw_data[1]}")
17
+ end
18
+
19
+ def local_zone_hours
20
+ return if raw_data[4].blank?
21
+ raw_data[4].to_i
22
+ end
23
+
24
+ def local_zone_minutes
25
+ return if raw_data[5].blank?
26
+ raw_data[5].to_i
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ module NmeaGps
2
+ VERSION = "0.0.1"
3
+ end
data/lib/nmea_gps.rb ADDED
@@ -0,0 +1,40 @@
1
+ require "pathname"
2
+ require "serialport"
3
+ require "ostruct"
4
+
5
+ require 'active_support'
6
+ require 'active_support/core_ext'
7
+ Time.zone = "UTC"
8
+
9
+ require "nmea_gps/version"
10
+ require "nmea_gps/config"
11
+ require "nmea_gps/gps"
12
+
13
+ require "nmea_gps/sentence_base"
14
+ Dir[Pathname(__FILE__).join("../sentences/*.rb")].each {|file| require file }
15
+
16
+ module Nmea
17
+
18
+ module UtcTimeable
19
+ protected
20
+ def hhmmss_to_local_time(text)
21
+ hh, mm, ss = text.chars.each_slice(2).collect{|chars| chars.join }
22
+ utc_today = Time.zone.today
23
+ Time.zone.local(utc_today.year, utc_today.month, utc_today.day, hh, mm, ss).
24
+ in_time_zone(Nmea::Config.time_zone)
25
+ end
26
+ end
27
+
28
+ module DddFormatable
29
+ protected
30
+ def dmm_to_ddd(dmm, direction)
31
+ dmm = dmm.to_f / 100
32
+ integer = dmm.to_i
33
+ decimal = dmm % 1
34
+
35
+ (integer + decimal / 60 * 100.0) * ("NE".include?(direction) ? 1 : -1)
36
+ end
37
+ end
38
+
39
+ end
40
+