ogn_client-ruby 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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0e4b42ab724fb2c9046c419ab356990d476e4dcc
4
+ data.tar.gz: 9c1617d22daa0fb63e936eb491acf2e6ff78a39e
5
+ SHA512:
6
+ metadata.gz: 68a2ca8b91f5ec145eb70ffa7cfc511b62ed324f97431a50232193e13b26bc90fb51b036030944b543935245a3900974c3d4b3fbeffac6a74d5bd9ffe34b12f3
7
+ data.tar.gz: 6a522636b0cdf6eb93dbde9bb588607474a57a988a64f7f9480a8b3be2457e52f04ccc4afda6d2cecf17940145ada2ec8f3af2bc49183ad6fb3afa991113b4aa
@@ -0,0 +1,12 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = space
5
+ indent_size = 2
6
+ end_of_line = lf
7
+ charset = utf-8
8
+ trim_trailing_whitespace = true
9
+ insert_final_newline = true
10
+
11
+ [*.md]
12
+ trim_trailing_whitespace = false
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .DS_Store
@@ -0,0 +1 @@
1
+ ruby-2.2.3
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.3
4
+ - 2.1.7
5
+ before_install: gem install bundler -v 1.10.6
@@ -0,0 +1,5 @@
1
+ ## 0.1.0
2
+
3
+ ### initial release
4
+ * OGN subscription
5
+ * parser for senders (aircraft), receivers and comments
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ if RUBY_PLATFORM.match /darwin/
6
+ gem 'guard'
7
+ gem 'guard-minitest'
8
+ gem 'minitest-osx'
9
+ end
@@ -0,0 +1,7 @@
1
+ clearing :on
2
+
3
+ guard :minitest do
4
+ watch(%r{^spec/(.*)_spec\.rb})
5
+ watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
6
+ watch(%r{^spec/spec_helper\.rb}) { 'spec' }
7
+ end
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Sven Schwyn
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,151 @@
1
+ # ogn_client-ruby
2
+
3
+ [OGN](http://glidernet.org) broadcasts aircraft positions as [APRS](http://www.aprs.org)/[APRS-IS](http://www.aprs-is.net) messages. This gem hooks into this stream of data and provides the necessary classes to parse the raw message strings into meaningful objects.
4
+
5
+ * Author: [Sven Schwyn](http://bitcetera.com)
6
+ * Homepage: https://github.com/svoop/ogn_client-ruby
7
+
8
+ :loudspeaker: A word from the shameless commerce division: Looking for a freelance Ruby developer? Contact Sven on http://bitcetera.com
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'ogn_client-ruby', require: 'ogn_client'
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself with:
23
+
24
+ $ gem install ogn_client-ruby
25
+
26
+ ## Usage
27
+
28
+ ### Subscribe to OGN
29
+
30
+ Choose a [valid callsign](http://www.aprs-is.net/Connecting.aspx#loginrules) and [appropriate filters](http://www.aprs-is.net/javAPRSFilter.aspx), then start listening to the broadcasted raw messages:
31
+
32
+ ```ruby
33
+ OGNClient::APRS.start(callsign: 'ROCT', filter: 'r/33/-97/200') do |aprs|
34
+ puts aprs.gets until aprs.eof?
35
+ end
36
+ ```
37
+
38
+ ### Parse Raw Message Strings
39
+
40
+ In the above example, each `aprs.gets` returns a raw message string. To decode this string, just pass it to the message parser:
41
+
42
+ ```ruby
43
+ OGNClient::Message.parse(aprs.gets)
44
+ ```
45
+
46
+ This factory method will return an instance of one of the following four classes.
47
+
48
+ :point_up: Refer to the wiki for an introduction to [OGN flavoured APRS](https://github.com/svoop/ogn_client-ruby/wiki) messages.
49
+
50
+ #### OGNClient::Sender
51
+
52
+ Senders are usually aircraft equipped with [FLARM](https://flarm.com) (anti-collision warning system) or similar devices which broadcast position data as RF beacons.
53
+
54
+ The data is converted into the metric system since [OGN](http://glidernet.org) is primarily made for gliders which mostly use the metric system for speed, climb rate and so forth.
55
+
56
+ Attributes:
57
+ * **callsign** - origin callsign
58
+ * **receiver** - receiver callsign
59
+ * **time** - UTZ/zulu time with date
60
+ * **longitude** - degrees from -180 (W) to 180 (E)
61
+ * **latitude** - degrees from -90 (S) to 90 (N)
62
+ * **altitude** - meters
63
+ * **heading** - degrees from 1 to 360
64
+ * **speed** - kilometers per hour
65
+ * **stealth_mode** - boolean (should always be false)
66
+ * **no_tracking** - boolean
67
+ * **sender_type** - see [SENDER_TYPES](https://github.com/svoop/ogn_client-ruby/blob/master/lib/ogn_client/messages/sender.rb)
68
+ * **address_type** - see [ADDRESS_TYPES](https://github.com/svoop/ogn_client-ruby/blob/master/lib/ogn_client/messages/sender.rb)
69
+ * **id** - device ID
70
+ * **climb_rate** - meters per second
71
+ * **turn_rate** - revolutions per minute
72
+ * **signal** - signal to noise ratio in decibel
73
+ * **errors** - number of CRC errors
74
+ * **frequency_offset** - kilohertz
75
+ * **gps_accuracy** - array [vertical meters, horizontal meters]
76
+ * **proximity** - array of callsign tails
77
+
78
+ #### OGNClient::Receiver
79
+
80
+ Receivers are little RF boxes which pick up the RF beacons from aircraft and relay them to the OGN servers as messages. They send their own status messages on a regular basis.
81
+
82
+ Attributes:
83
+ * **callsign** - origin callsign
84
+ * **receiver** - receiver callsign
85
+ * **time** - UTZ/zulu time with date
86
+ * **longitude** - degrees from -180 (W) to 180 (E)
87
+ * **latitude** - degrees from -90 (S) to 90 (N)
88
+ * **altitude** - meters
89
+ * **heading** - degrees from 1 to 360
90
+ * **speed** - kilometers per hour
91
+ * **version** - software version "major.minor.patch"
92
+ * **platform** - e.g. :arm
93
+ * **cpu_load** - as reported by "uptime"
94
+ * **cpu_temperature** - in degrees celsius
95
+ * **ram_free** - megabytes
96
+ * **ram_total** - megabytes
97
+ * **ntp_offset** - milliseconds
98
+ * **ntp_correction** - parts-per-million
99
+ * **signal** - signal-to-noise ratio in decibel
100
+
101
+ #### OGNClient::Comment
102
+
103
+ Comments are sent on a regular basis to keep the connection alive.
104
+
105
+ Attribute:
106
+ * **comment** - raw message with the comment marker stripped
107
+
108
+ #### OGNClient::Message
109
+
110
+ Generic message objects are only used as a fallback in case the raw message string could not be parsed into one of the above three [OGN](http://glidernet.org) object types. If this happens, either the [OGN](http://glidernet.org) specifications have changed, the message is invalid or you have found a bug in the parser code. You can choose to ignore such messages or to store them for debugging and replaying once the bug is fixed.
111
+
112
+ ## Debugging
113
+
114
+ To get additional debug messages, either invoke Ruby with the `-d` flag or set the global debug variable explicitly:
115
+
116
+ ```ruby
117
+ $DEBUG = true
118
+ ```
119
+
120
+ ## Development
121
+
122
+ Check out the repository, install the dependencies and run the test suite:
123
+
124
+ $ git clone git@github.com:svoop/ogn_client-ruby.git
125
+ $ bin/setup
126
+ $ rake
127
+
128
+ If you are on Mac OS X, +guard+, +guard-minitest+ and +minitest-osx+ are also installed and therefore you get the test results as notifications by running a guard watchdog with:
129
+
130
+ $ guard
131
+
132
+ To play around with the gem:
133
+
134
+ $ bin/console
135
+
136
+ And to install this gem onto your local machine:
137
+
138
+ $ bundle exec rake install
139
+
140
+ ## Contributing
141
+
142
+ Bug reports and pull requests are welcome on GitHub at https://github.com/svoop/ogn_client-ruby
143
+
144
+ ## Feature Brainstorming
145
+
146
+ * configuration option to switch between metric and aeronautical units
147
+ * more data sources such as ADS-B
148
+
149
+ ## License
150
+
151
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,11 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'lib'
7
+ t.test_files = FileList['spec/lib/**/*_spec.rb']
8
+ t.verbose = true
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "ogn_client"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ require "pry"
10
+ Pry.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here.
@@ -0,0 +1,9 @@
1
+ require 'socket'
2
+
3
+ require_relative 'ogn_client/version'
4
+ require_relative 'ogn_client/debug'
5
+ require_relative 'ogn_client/aprs'
6
+ require_relative 'ogn_client/message'
7
+ require_relative 'ogn_client/messages/comment'
8
+ require_relative 'ogn_client/messages/receiver'
9
+ require_relative 'ogn_client/messages/sender'
@@ -0,0 +1,69 @@
1
+ module OGNClient
2
+
3
+ # Minimalistic APRS implementation for OGN
4
+ #
5
+ # Use a unique all-uppercase callsign to create the instance and then
6
+ # connect with or without filters.
7
+ #
8
+ # OGNClient::APRS.start(callsign: 'OGNC', filter: 'r/33/-97/200') do |aprs|
9
+ # puts aprs.gets until aprs.eof?
10
+ # end
11
+ #
12
+ # See http://www.aprs-is.net/javAPRSFilter.aspx for available filters.
13
+ class APRS
14
+
15
+ SERVER = 'aprs.glidernet.org'
16
+ PORT_FILTERED = 14580
17
+ PORT_UNFILTERED = 10152
18
+ AGENT = "#{RUBY_ENGINE}-ogn_client"
19
+
20
+ attr_reader :callsign, :filter, :socket
21
+
22
+ def initialize(callsign:, filter: nil)
23
+ @callsign = callsign.upcase
24
+ @filter = filter
25
+ @port = filter ? PORT_FILTERED : PORT_UNFILTERED
26
+ end
27
+
28
+ def self.start(callsign:, filter: nil, &block)
29
+ new(callsign: callsign, filter: filter).start(&block)
30
+ end
31
+
32
+ def start
33
+ @socket = TCPSocket.open SERVER, @port
34
+ @socket.puts handshake
35
+ if block_given?
36
+ begin
37
+ return yield(@socket)
38
+ ensure
39
+ @socket.close
40
+ end
41
+ end
42
+ self
43
+ end
44
+
45
+ private
46
+
47
+ # Calculate the passcode from the callsign
48
+ def passcode(readonly: true)
49
+ if readonly
50
+ -1
51
+ else
52
+ @callsign.split('-').first.chars.reduce([0x73E2, true]) do |hash, char|
53
+ [hash.first ^ (hash.last ? char.ord << 8 : char.ord), !hash.last]
54
+ end.first & 0x7FFF
55
+ end
56
+ end
57
+
58
+ # Build the handshake to connect to the server
59
+ def handshake(readonly: true)
60
+ {
61
+ user: @callsign,
62
+ pass: passcode(readonly: readonly),
63
+ vers: "#{AGENT} #{OGNClient::VERSION}",
64
+ filter: @filter
65
+ }.map { |k, v| "#{k} #{v}" if v }.compact.join(' ')
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,9 @@
1
+ module OGNClient
2
+ class << self
3
+
4
+ def debug(message)
5
+ puts "DEBUG: #{message}" if $DEBUG
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,112 @@
1
+ module OGNClient
2
+
3
+ # Generic OGN flavoured APRS parser
4
+ #
5
+ # You can pass any raw OGN flavoured APRS message string to the +parse+ class
6
+ # method and receive an instance of the appropriate subclass (+Comment+,
7
+ # +Receiver+ or +Sender+) or +nil+ if the message string could not be parsed.
8
+ #
9
+ # Comment example:
10
+ # raw = "# aprsc 2.0.14-g28c5a6a 29 Jun 2014 07:46:15 GMT GLIDERN1 37.187.40.234:14580"
11
+ # obj = OGNClient::Message.parse(raw) # => #<OGNClient::Comment:0x007feaf1012898>
12
+ # obj.comment # => "aprsc 2.0.14-g28c5a6a 29 Jun 2014 07:46:15 GMT GLIDERN1 37.187.40.234:14580"
13
+ #
14
+ # Sender example:
15
+ # raw = "FLRDF0A52>APRS,qAS,LSTB:/220132h4658.70N/00707.72Ez090/054/A=001424 id06DF0A52 +020fpm +0.0rot 55.2dB 0e -6.2kHz"
16
+ # obj = OGNClient::Message.parse(raw) # => #<OGNClient::Sender:0x007feaec1daba8>
17
+ # obj.id # => "DF0A52"
18
+ #
19
+ # Malformed example:
20
+ # raw = "FOOBAR>not a valid message"
21
+ # obj = OGNClient::Message.parse(raw) # => nil
22
+ class Message
23
+
24
+ POSITION_PATTERN = %r(^
25
+ (?<callsign>\w+?)>APRS,.+?,
26
+ (?<receiver>\w+):/
27
+ (?<time>\d{6})h
28
+ (?<latitude>\d{4}\.\d{2}[NS]).
29
+ (?<longitude>\d{5}\.\d{2}[EW]).
30
+ (?:(?<heading>\d{3})/(?<speed>\d{3}))?.*?
31
+ /A=(?<altitude>\d{6})\s
32
+ (?:!W((?<latitude_enhancement>\d)(?<longitude_enhancement>\d))!)?
33
+ )x
34
+
35
+ attr_reader :raw
36
+ attr_reader :callsign # origin callsign
37
+ attr_reader :receiver # receiver callsign
38
+ attr_reader :time # zulu time with date
39
+ attr_reader :longitude # degrees from -180 (W) to 180 (E)
40
+ attr_reader :latitude # degrees from -90 (S) to 90 (N)
41
+ attr_reader :altitude # meters
42
+ attr_reader :heading # degrees from 1 to 360
43
+ attr_reader :speed # kilometers per hour
44
+
45
+ def self.parse(raw)
46
+ OGNClient::Sender.new.parse(raw) ||
47
+ OGNClient::Receiver.new.parse(raw) ||
48
+ OGNClient::Comment.new.parse(raw) ||
49
+ new.parse(raw)
50
+ end
51
+
52
+ def parse(raw)
53
+ @raw = raw
54
+ raw.match(POSITION_PATTERN) do |match|
55
+ %i(callsign receiver time altitude).each do |attr|
56
+ send "#{attr}=", match[attr]
57
+ end
58
+ self.heading = match[:heading] if match[:heading] && match[:heading] != '000'
59
+ self.speed = match[:speed] if match[:speed] && match[:heading] != '000'
60
+ self.longitude = [match[:longitude], match[:longitude_enhancement]]
61
+ self.latitude = [match[:latitude], match[:latitude_enhancement]]
62
+ self
63
+ end || OGNClient.debug("invalid message: `#{@raw}'")
64
+ self
65
+ end
66
+
67
+ def to_s
68
+ @raw
69
+ end
70
+
71
+ private
72
+
73
+ def callsign=(raw)
74
+ @callsign = raw
75
+ end
76
+
77
+ def receiver=(raw)
78
+ @receiver = raw
79
+ end
80
+
81
+ def time=(raw)
82
+ now = Time.now.utc
83
+ time = Time.new(now.year, now.month, now.day, raw[0,2], raw[2,2], raw[4,2], 0)
84
+ time -= 86400 if time > now # adjust date of beacons sent just before midnight
85
+ @time = time
86
+ end
87
+
88
+ def longitude=(raw)
89
+ raw.first.match /(\d{3})([\d.]+)([EW])/
90
+ @longitude = (($1.to_f + ("#{$2}#{raw.last}".to_f / 60)) * ($3 == 'E' ? 1 : -1)).round(6)
91
+ end
92
+
93
+ def latitude=(raw)
94
+ raw.first.match /(\d{2})([\d.]+)([NS])/
95
+ @latitude = (($1.to_f + ("#{$2}#{raw.last}".to_f / 60)) * ($3 == 'N' ? 1 : -1)).round(6)
96
+ end
97
+
98
+ def altitude=(raw)
99
+ @altitude = (raw.to_i / 3.2808).round
100
+ end
101
+
102
+ def heading=(raw)
103
+ @heading = raw.to_i
104
+ end
105
+
106
+ def speed=(raw)
107
+ @speed = (raw.to_i * 1.852).round
108
+ end
109
+
110
+ end
111
+
112
+ end
@@ -0,0 +1,27 @@
1
+ module OGNClient
2
+
3
+ class Comment < Message
4
+
5
+ COMMENT_PATTERN = %r(^
6
+ \#\s*(?<comment>.*?)\s*
7
+ $)x
8
+
9
+ attr_reader :comment # free form text comment
10
+
11
+ def parse(raw)
12
+ @raw = raw
13
+ raw.match COMMENT_PATTERN do |match|
14
+ self.comment = match[:comment]
15
+ self
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def comment=(raw)
22
+ @comment = raw
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,74 @@
1
+ module OGNClient
2
+
3
+ class Receiver < Message
4
+
5
+ RECEIVER_PATTERN = %r(
6
+ v(?<version>\d+\.\d+\.\d+)\.(?<platform>.+?)\s
7
+ CPU:(?<cpu_load>[\d.]+)\s
8
+ RAM:(?<ram_free>[\d.]+)/(?<ram_total>[\d.]+)MB\s
9
+ NTP:(?<ntp_offset>[\d.]+)ms/(?<ntp_correction>[+-][\d.]+)ppm\s
10
+ (?:(?<cpu_temperature>[+-][\d.]+)C\s)?
11
+ RF:(?<signal>[+-][\d.]+)dB
12
+ $)x
13
+
14
+ attr_reader :version # software version "major.minor.patch"
15
+ attr_reader :platform # e.g. "ARM"
16
+ attr_reader :cpu_load # as reported by "uptime"
17
+ attr_reader :cpu_temperature # degrees celsius
18
+ attr_reader :ram_free # megabytes
19
+ attr_reader :ram_total # megabytes
20
+ attr_reader :ntp_offset # milliseconds
21
+ attr_reader :ntp_correction # parts-per-million
22
+ attr_reader :signal # signal-to-noise ratio in decibel
23
+
24
+ def parse(raw)
25
+ raw.match RECEIVER_PATTERN do |match|
26
+ return unless super
27
+ %i(version platform cpu_load cpu_temperature ram_free ram_total ntp_offset ntp_correction signal).each do |attr|
28
+ send("#{attr}=", match[attr]) if match[attr]
29
+ end
30
+ self
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def version=(raw)
37
+ @version = raw
38
+ end
39
+
40
+ def platform=(raw)
41
+ @platform = raw.to_sym.downcase
42
+ end
43
+
44
+ def cpu_load=(raw)
45
+ @cpu_load = raw.to_f.round(2)
46
+ end
47
+
48
+ def cpu_temperature=(raw)
49
+ @cpu_temperature = raw.to_f.round(2)
50
+ end
51
+
52
+ def ram_free=(raw)
53
+ @ram_free = raw.to_f.round(2)
54
+ end
55
+
56
+ def ram_total=(raw)
57
+ @ram_total = raw.to_f.round(2)
58
+ end
59
+
60
+ def ntp_offset=(raw)
61
+ @ntp_offset = raw.to_f.round(2)
62
+ end
63
+
64
+ def ntp_correction=(raw)
65
+ @ntp_correction = raw.to_f.round(2)
66
+ end
67
+
68
+ def signal=(raw)
69
+ @signal = raw.to_f.round(3)
70
+ end
71
+
72
+ end
73
+
74
+ end
@@ -0,0 +1,107 @@
1
+ module OGNClient
2
+
3
+ class Sender < Message
4
+
5
+ SENDER_PATTERN = %r(
6
+ id(?<details>\w{2})(?<id>\w+?)\s
7
+ (?<climb_rate>[+-]\d+?)fpm\s
8
+ (?<turn_rate>[+-][\d.]+?)rot\s
9
+ (?<signal>[\d.]+?)dB\s
10
+ (?<errors>\d+)e\s
11
+ (?<frequency_offset>[+-][\d.]+?)kHz\s?
12
+ (?:gps(?<gps_accuracy>\d+x\d+)\s?)?
13
+ (?:hear(?<proximity>.+))?
14
+ $)x
15
+
16
+ SENDER_TYPES = {
17
+ 1 => :glider,
18
+ 2 => :tow_plane,
19
+ 3 => :helicopter_rotorcraft,
20
+ 4 => :parachute,
21
+ 5 => :drop_plane,
22
+ 6 => :hang_glider,
23
+ 7 => :para_glider,
24
+ 8 => :powered_aircraft,
25
+ 9 => :jet_aircraft,
26
+ 10 => :ufo,
27
+ 11 => :balloon,
28
+ 12 => :airship,
29
+ 13 => :uav,
30
+ 15 => :static_object
31
+ }
32
+
33
+ ADDRESS_TYPES = {
34
+ 0 => :random,
35
+ 1 => :icao,
36
+ 2 => :flarm,
37
+ 3 => :ogn
38
+ }
39
+
40
+ attr_reader :stealth_mode # boolean
41
+ attr_reader :no_tracking # boolean
42
+ attr_reader :sender_type # see SENDER_TYPES
43
+ attr_reader :address_type # see ADDRESS_TYPES
44
+ attr_reader :id # device ID
45
+ attr_reader :climb_rate # meters per second
46
+ attr_reader :turn_rate # revolutions per minute
47
+ attr_reader :signal # signal to noise ratio in decibel
48
+ attr_reader :errors # number of CRC errors
49
+ attr_reader :frequency_offset # kilohertz
50
+ attr_reader :gps_accuracy # array [vertical meters, horizontal meters]
51
+ attr_reader :proximity # array of callsign tails
52
+
53
+ def parse(raw)
54
+ raw.match SENDER_PATTERN do |match|
55
+ return unless super
56
+ %i(details id climb_rate turn_rate signal errors frequency_offset gps_accuracy proximity).each do |attr|
57
+ send("#{attr}=", match[attr]) if match[attr]
58
+ end
59
+ self
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def details=(raw)
66
+ byte = raw.to_i(16)
67
+ @stealth_mode = !(byte & 0b10000000).zero?
68
+ @no_tracking = !(byte & 0b01000000).zero?
69
+ @address_type = ADDRESS_TYPES.fetch((byte & 0b00000011), :unknown)
70
+ @sender_type = SENDER_TYPES.fetch((byte & 0b00111100) >> 2, :unknown)
71
+ end
72
+
73
+ def id=(raw)
74
+ @id = raw
75
+ end
76
+
77
+ def climb_rate=(raw)
78
+ @climb_rate = (raw.to_i / 60.0 / 3.2808).round(1)
79
+ end
80
+
81
+ def turn_rate=(raw)
82
+ @turn_rate = (raw.to_i / 4.0).round(1)
83
+ end
84
+
85
+ def signal=(raw)
86
+ @signal = raw.to_f.round(1)
87
+ end
88
+
89
+ def errors=(raw)
90
+ @errors = raw.to_i
91
+ end
92
+
93
+ def frequency_offset=(raw)
94
+ @frequency_offset = raw.to_f.round(1)
95
+ end
96
+
97
+ def gps_accuracy=(raw)
98
+ @gps_accuracy = raw.split('x').map(&:to_i)
99
+ end
100
+
101
+ def proximity=(raw)
102
+ @proximity = raw.split(/\shear/)
103
+ end
104
+
105
+ end
106
+
107
+ end
@@ -0,0 +1,3 @@
1
+ module OGNClient
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ogn_client/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'ogn_client-ruby'
8
+ spec.version = OGNClient::VERSION
9
+ spec.authors = ['Sven Schwyn']
10
+ spec.email = ['ruby@bitcetera.com']
11
+ spec.summary = %q{Subscriber and parser for APRS messages from OGN}
12
+ spec.description = %q{OGN (glidernet.org) broadcasts aircraft positions as APRS/APRS-IS messages. This gem hooks into this stream of data and provides the necessary classes to parse the raw message strings into meaningful objects.}
13
+ spec.homepage = 'https://github.com/svoop/ogn_client-ruby'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = 'exe'
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.required_ruby_version = '>= 2.1'
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.10'
24
+ spec.add_development_dependency 'rake', '~> 10.0'
25
+ spec.add_development_dependency 'pry'
26
+ spec.add_development_dependency 'minitest'
27
+ spec.add_development_dependency 'minitest-reporters'
28
+ spec.add_development_dependency 'minitest-matchers'
29
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ogn_client-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sven Schwyn
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-12-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest-reporters
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: minitest-matchers
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: OGN (glidernet.org) broadcasts aircraft positions as APRS/APRS-IS messages.
98
+ This gem hooks into this stream of data and provides the necessary classes to parse
99
+ the raw message strings into meaningful objects.
100
+ email:
101
+ - ruby@bitcetera.com
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - ".editorconfig"
107
+ - ".gitignore"
108
+ - ".ruby-version"
109
+ - ".travis.yml"
110
+ - CHANGELOG.md
111
+ - Gemfile
112
+ - Guardfile
113
+ - LICENSE.txt
114
+ - README.md
115
+ - Rakefile
116
+ - bin/console
117
+ - bin/setup
118
+ - lib/ogn_client.rb
119
+ - lib/ogn_client/aprs.rb
120
+ - lib/ogn_client/debug.rb
121
+ - lib/ogn_client/message.rb
122
+ - lib/ogn_client/messages/comment.rb
123
+ - lib/ogn_client/messages/receiver.rb
124
+ - lib/ogn_client/messages/sender.rb
125
+ - lib/ogn_client/version.rb
126
+ - ogn_client-ruby.gemspec
127
+ homepage: https://github.com/svoop/ogn_client-ruby
128
+ licenses:
129
+ - MIT
130
+ metadata: {}
131
+ post_install_message:
132
+ rdoc_options: []
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '2.1'
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ requirements: []
146
+ rubyforge_project:
147
+ rubygems_version: 2.4.5.1
148
+ signing_key:
149
+ specification_version: 4
150
+ summary: Subscriber and parser for APRS messages from OGN
151
+ test_files: []
152
+ has_rdoc: