ogn_client-ruby 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +12 -0
- data/.gitignore +10 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +9 -0
- data/Guardfile +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +151 -0
- data/Rakefile +11 -0
- data/bin/console +10 -0
- data/bin/setup +7 -0
- data/lib/ogn_client.rb +9 -0
- data/lib/ogn_client/aprs.rb +69 -0
- data/lib/ogn_client/debug.rb +9 -0
- data/lib/ogn_client/message.rb +112 -0
- data/lib/ogn_client/messages/comment.rb +27 -0
- data/lib/ogn_client/messages/receiver.rb +74 -0
- data/lib/ogn_client/messages/sender.rb +107 -0
- data/lib/ogn_client/version.rb +3 -0
- data/ogn_client-ruby.gemspec +29 -0
- metadata +152 -0
checksums.yaml
ADDED
@@ -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
|
data/.editorconfig
ADDED
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.2.3
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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).
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
data/lib/ogn_client.rb
ADDED
@@ -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,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,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:
|