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.
- 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:
|