nmea_plus 1.0.7 → 1.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +13 -6
- data/lib/nmea_plus/message/ais/vdm.rb +1 -0
- data/lib/nmea_plus/message/ais/vdm_payload/payload.rb +198 -0
- data/lib/nmea_plus/message/ais/vdm_payload/vdm_msg.rb +21 -147
- data/lib/nmea_plus/message/ais/vdm_payload/vdm_msg1.rb +28 -33
- data/lib/nmea_plus/message/ais/vdm_payload/vdm_msg18.rb +5 -24
- data/lib/nmea_plus/message/ais/vdm_payload/vdm_msg21.rb +2 -12
- data/lib/nmea_plus/message/ais/vdm_payload/vdm_msg27.rb +30 -0
- data/lib/nmea_plus/message/ais/vdm_payload/vdm_msg4.rb +2 -13
- data/lib/nmea_plus/message/ais/vdm_payload/vdm_msg5.rb +1 -1
- data/lib/nmea_plus/message/ais/vdm_payload/vdm_msg8.rb +48 -1
- data/lib/nmea_plus/message/ais/vdm_payload/vdm_msg8d1f31.rb +139 -0
- data/lib/nmea_plus/message/ais/vdm_payload/vdm_msg8d366f56.rb +29 -0
- data/lib/nmea_plus/message/ais/vdm_payload/vdm_msg9.rb +2 -13
- data/lib/nmea_plus/message/nmea/aam.rb +1 -1
- data/lib/nmea_plus/message/nmea/alm.rb +1 -0
- data/lib/nmea_plus/message/nmea/apa.rb +1 -0
- data/lib/nmea_plus/message/nmea/apb.rb +1 -0
- data/lib/nmea_plus/message/nmea/bod.rb +1 -0
- data/lib/nmea_plus/message/nmea/bwc.rb +1 -0
- data/lib/nmea_plus/message/nmea/bwr.rb +1 -0
- data/lib/nmea_plus/message/nmea/bww.rb +1 -0
- data/lib/nmea_plus/message/nmea/dbk.rb +1 -0
- data/lib/nmea_plus/message/nmea/dbs.rb +1 -0
- data/lib/nmea_plus/message/nmea/dbt.rb +1 -0
- data/lib/nmea_plus/message/nmea/dcn.rb +1 -0
- data/lib/nmea_plus/message/nmea/dpt.rb +1 -0
- data/lib/nmea_plus/message/nmea/dtm.rb +1 -0
- data/lib/nmea_plus/message/nmea/fsi.rb +1 -0
- data/lib/nmea_plus/message/nmea/gbs.rb +1 -0
- data/lib/nmea_plus/message/nmea/gga.rb +1 -1
- data/lib/nmea_plus/message/nmea/glc.rb +1 -0
- data/lib/nmea_plus/message/nmea/gll.rb +1 -0
- data/lib/nmea_plus/message/nmea/gns.rb +1 -0
- data/lib/nmea_plus/message/nmea/grs.rb +1 -0
- data/lib/nmea_plus/message/nmea/gsa.rb +1 -0
- data/lib/nmea_plus/message/nmea/gst.rb +1 -0
- data/lib/nmea_plus/message/nmea/gsv.rb +1 -0
- data/lib/nmea_plus/message/nmea/gtd.rb +1 -0
- data/lib/nmea_plus/message/nmea/gxa.rb +1 -0
- data/lib/nmea_plus/message/nmea/hdg.rb +1 -0
- data/lib/nmea_plus/message/nmea/hdm.rb +1 -0
- data/lib/nmea_plus/message/nmea/hdt.rb +1 -0
- data/lib/nmea_plus/message/nmea/hfb.rb +1 -0
- data/lib/nmea_plus/message/nmea/hsc.rb +1 -0
- data/lib/nmea_plus/message/nmea/its.rb +1 -0
- data/lib/nmea_plus/message/nmea/lcd.rb +1 -0
- data/lib/nmea_plus/message/nmea/msk.rb +1 -0
- data/lib/nmea_plus/message/nmea/mss.rb +1 -0
- data/lib/nmea_plus/message/nmea/mtw.rb +1 -0
- data/lib/nmea_plus/message/nmea/mwv.rb +1 -0
- data/lib/nmea_plus/message/nmea/oln.rb +1 -0
- data/lib/nmea_plus/message/nmea/osd.rb +1 -0
- data/lib/nmea_plus/message/nmea/pashr.rb +1 -0
- data/lib/nmea_plus/message/nmea/r00.rb +1 -0
- data/lib/nmea_plus/message/nmea/rma.rb +1 -0
- data/lib/nmea_plus/message/nmea/rmb.rb +1 -0
- data/lib/nmea_plus/message/nmea/rmc.rb +1 -0
- data/lib/nmea_plus/message/nmea/rot.rb +1 -0
- data/lib/nmea_plus/message/nmea/rpm.rb +1 -0
- data/lib/nmea_plus/message/nmea/rsa.rb +1 -0
- data/lib/nmea_plus/message/nmea/rsd.rb +1 -0
- data/lib/nmea_plus/message/nmea/rte.rb +1 -0
- data/lib/nmea_plus/message/nmea/sfi.rb +1 -0
- data/lib/nmea_plus/message/nmea/stn.rb +1 -0
- data/lib/nmea_plus/message/nmea/tds.rb +1 -0
- data/lib/nmea_plus/message/nmea/tfi.rb +1 -0
- data/lib/nmea_plus/message/nmea/tpc.rb +1 -0
- data/lib/nmea_plus/message/nmea/tpr.rb +1 -0
- data/lib/nmea_plus/message/nmea/tpt.rb +1 -0
- data/lib/nmea_plus/message/nmea/trf.rb +1 -0
- data/lib/nmea_plus/message/nmea/ttm.rb +1 -0
- data/lib/nmea_plus/message/nmea/vbw.rb +1 -0
- data/lib/nmea_plus/message/nmea/vdr.rb +1 -0
- data/lib/nmea_plus/message/nmea/vhw.rb +1 -0
- data/lib/nmea_plus/message/nmea/vlw.rb +1 -0
- data/lib/nmea_plus/message/nmea/vpw.rb +1 -0
- data/lib/nmea_plus/message/nmea/vtg.rb +1 -0
- data/lib/nmea_plus/message/nmea/vwr.rb +1 -0
- data/lib/nmea_plus/message/nmea/wcv.rb +1 -0
- data/lib/nmea_plus/message/nmea/wnc.rb +1 -0
- data/lib/nmea_plus/message/nmea/wpl.rb +1 -0
- data/lib/nmea_plus/message/nmea/xdr.rb +1 -0
- data/lib/nmea_plus/message/nmea/xte.rb +1 -0
- data/lib/nmea_plus/message/nmea/xtr.rb +1 -0
- data/lib/nmea_plus/message/nmea/zda.rb +1 -0
- data/lib/nmea_plus/message/nmea/zfo.rb +1 -0
- data/lib/nmea_plus/message/nmea/ztg.rb +1 -0
- data/lib/nmea_plus/version.rb +1 -1
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad7de05d0b6299139bf89734517185b1056c2055
|
4
|
+
data.tar.gz: d77eced056aac4fb30998039e41fc9bcdc02e5a5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 337635824791c01b604b2e8027db96711375ad5fe521568953d3c5daac1e9df5605b79434b54ce90b474ff683fd5c532a18ac969585f9cf6a3dc3fc1ae2e42be
|
7
|
+
data.tar.gz: 91dd8c299619df14559f74120e56141d790d413557cd2d8be038f2b0f4df55b21fb42e78561d865b0d4b3a04c13ddd76278fc2cfb535399a33084ce72ee3f4ab
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/nmea_plus.svg)](https://rubygems.org/gems/nmea_plus)
|
4
4
|
[![Build Status](https://travis-ci.org/ifreecarve/nmea_plus.svg)](https://travis-ci.org/ifreecarve/nmea_plus)
|
5
|
-
[![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://www.rubydoc.info/gems/nmea_plus/1.0.
|
5
|
+
[![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://www.rubydoc.info/gems/nmea_plus/1.0.8)
|
6
6
|
|
7
7
|
[NMEA Plus](https://github.com/ifreecarve/nmea_plus) is a Ruby gem for parsing and decoding "GPS" messages: NMEA, AIS, and any other similar formats of short messaging typically used by marine equipment. It provides convenient access (by name) to the fields of each message type, and a stream reader designed for use with Ruby Blocks.
|
8
8
|
|
@@ -48,7 +48,7 @@ end
|
|
48
48
|
```
|
49
49
|
|
50
50
|
|
51
|
-
Of course, decoding in practice is more complex than that. Some messages can have multiple parts, and AIS messages have their own complicated payload. NMEAPlus provides a SourceDecoder
|
51
|
+
Of course, decoding in practice is more complex than that. Some messages can have multiple parts, and AIS messages have their own complicated payload. NMEAPlus provides a `SourceDecoder` class that operates on `IO` objects (anything with `each_line` support) -- a `File`, `SerialPort`, etc. You can iterate over each message (literally `each_message`), or receive only fully assembled multipart messages by iterating over `each_complete_message`.
|
52
52
|
|
53
53
|
```ruby
|
54
54
|
require 'nmea_plus'
|
@@ -86,6 +86,8 @@ end
|
|
86
86
|
|
87
87
|
## Design documents
|
88
88
|
|
89
|
+
### NMEA
|
90
|
+
|
89
91
|
This gem was coded to accept the standard NMEA messages defined in the unoffical spec found here:
|
90
92
|
http://www.catb.org/gpsd/NMEA.txt
|
91
93
|
|
@@ -93,18 +95,23 @@ Because the message types are standard, if no override is found for a particular
|
|
93
95
|
|
94
96
|
> AAM, ALM, APA, APB, BOD, BWC, BWR, BWW, DBK, DBS, DBT, DCN, DPT, DTM, FSI, GBS, GGA, GLC, GLL, GNS, GRS, GSA, GST, GSV, GTD, GXA, HDG, HDM, HDT, HFB, HSC, ITS, LCD, MSK, MSS, MTW, MWV, OLN, OSD, R00, RMA, RMB, RMC, ROT, RPM, RSA, RSD, RTE, SFI, STN, TDS, TFI, TPC, TPR, TPT, TRF, TTM, VBW, VDR, VHW, VLW, VPW, VTG, VWR, WCV, WNC, WPL, XDR, XTE, XTR, ZDA, ZFO, ZTG
|
95
97
|
|
98
|
+
Support for proprietary NMEA messages is also possible. PASHR is included as proof-of-concept.
|
99
|
+
|
100
|
+
### AIS
|
96
101
|
|
97
|
-
|
102
|
+
AIS message type definitions were implemented from the unofficial spec found here:
|
98
103
|
http://catb.org/gpsd/AIVDM.html
|
99
104
|
|
100
|
-
|
105
|
+
Currently, the following AIVDM message types are supported:
|
106
|
+
|
107
|
+
> 1, 2, 3, 4, 5, 8, 9, 12, 14, 18, 19, 20, 21, 24, 27
|
108
|
+
> Type 8 subtypes for DAC/FID: 1/31, 366/56, 366/57
|
101
109
|
|
102
|
-
Support for proprietary messages is also possible. PASHR is included as proof-of-concept.
|
103
110
|
|
104
111
|
|
105
112
|
## Disclaimer
|
106
113
|
|
107
|
-
This module was written from information scraped together on the web, not from testing on actual devices. Please don't entrust your life or the safety of
|
114
|
+
This module was written from information scraped together on the web, not from testing on actual devices. A lack of test cases -- especially for more obscure message types -- is a barrier to completeness. Please don't entrust your life or the safety of a ship to this code without doing your own rigorous testing.
|
108
115
|
|
109
116
|
|
110
117
|
## Author
|
@@ -28,6 +28,7 @@ require_relative "vdm_payload/vdm_msg18" # also includes 19
|
|
28
28
|
require_relative "vdm_payload/vdm_msg20"
|
29
29
|
require_relative "vdm_payload/vdm_msg21"
|
30
30
|
require_relative "vdm_payload/vdm_msg24"
|
31
|
+
require_relative "vdm_payload/vdm_msg27"
|
31
32
|
|
32
33
|
module NMEAPlus
|
33
34
|
module Message
|
@@ -0,0 +1,198 @@
|
|
1
|
+
|
2
|
+
module NMEAPlus
|
3
|
+
module Message
|
4
|
+
module AIS
|
5
|
+
module VDMPayload
|
6
|
+
# Basic tools for interpreting the armored payload encoding
|
7
|
+
class Payload
|
8
|
+
|
9
|
+
def initialize; end
|
10
|
+
|
11
|
+
# @return [String] The raw "armored payload" in the original message
|
12
|
+
attr_accessor :payload_bitstring
|
13
|
+
|
14
|
+
# @return [Integer] The number of padding characters required to bring the payload to a 6 bit boundary
|
15
|
+
attr_accessor :fill_bits
|
16
|
+
|
17
|
+
# make our own shortcut syntax for payload attributes
|
18
|
+
# @param name [String] What the accessor will be called
|
19
|
+
# @param start_bit [Integer] The index of first bit of this field in the payload
|
20
|
+
# @param length [Integer] The number of bits in this field
|
21
|
+
# @param formatter [Symbol] The symbol for the formatting function to apply to the field (optional)
|
22
|
+
# @param fmt_arg Any argument necessary for the formatting function
|
23
|
+
# @param fmt_arg2 Any other argument necessary for the formatting function
|
24
|
+
# @param fmt_arg3 Any other argument necessary for the formatting function
|
25
|
+
# @macro [attach] payload_reader
|
26
|
+
# @!attribute [r] $1
|
27
|
+
# @return The field defined by the $3 bits starting at payload bit $2, formatted with the function {#$4}($5, $6, $7)
|
28
|
+
def self.payload_reader(name, start_bit, length, formatter, fmt_arg = nil, fmt_arg2 = nil, fmt_arg3 = nil)
|
29
|
+
args = [start_bit, length]
|
30
|
+
args << fmt_arg unless fmt_arg.nil?
|
31
|
+
args << fmt_arg2 unless fmt_arg2.nil?
|
32
|
+
args << fmt_arg3 unless fmt_arg3.nil?
|
33
|
+
self.class_eval("def #{name};#{formatter}(#{args.join(', ')});end")
|
34
|
+
end
|
35
|
+
|
36
|
+
# Convert 6-bit ascii to a character, according to http://catb.org/gpsd/AIVDM.html#_ais_payload_data_types
|
37
|
+
# @param ord [Integer] The 6-bit ascii code
|
38
|
+
# @return [String] the character for that code
|
39
|
+
def _6b_ascii(ord)
|
40
|
+
'@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !"#$%&\'()*+,-./0123456789:;<=>?'[ord]
|
41
|
+
end
|
42
|
+
|
43
|
+
# Access part of the payload. If there aren't bytes, there, return nil
|
44
|
+
# else execute a block
|
45
|
+
# @param start [Integer] The index of the first bit in the payload field
|
46
|
+
# @param length [Integer] The number of bits in the payload field
|
47
|
+
# @return Nil or whatever is yielded by the block
|
48
|
+
# @yield [String] A binary coded string ("010010101" etc)
|
49
|
+
def _access(start, length)
|
50
|
+
part = @payload_bitstring[start, length]
|
51
|
+
return nil if part.nil? || part.empty?
|
52
|
+
yield part
|
53
|
+
end
|
54
|
+
|
55
|
+
# pull out 6b chunks from the payload, then convert those to their more familar characters
|
56
|
+
# @param start [Integer] The index of the first bit in the payload field
|
57
|
+
# @param length [Integer] The number of bits in the payload field
|
58
|
+
# @return [String]
|
59
|
+
def _6b_string(start, length)
|
60
|
+
_bit_slices(start, length, 6).to_a.map(&:join).map { |x| _6b_ascii(x.to_i(2)) }.join
|
61
|
+
end
|
62
|
+
|
63
|
+
# pull out 8b chunks from the payload, then convert those to their more familar characters
|
64
|
+
# @param start [Integer] The index of the first bit in the payload field
|
65
|
+
# @param length [Integer] The number of bits in the payload field
|
66
|
+
# @return [String]
|
67
|
+
def _8b_data_string(start, length)
|
68
|
+
_bit_slices(start, length, 8).to_a.map(&:join).map { |x| x.to_i(2).chr }.join
|
69
|
+
end
|
70
|
+
|
71
|
+
# Slice a part of the payload into binary chunks of a given size
|
72
|
+
# @param start [Integer] The index of the first bit in the payload field
|
73
|
+
# @param length [Integer] The number of bits in the payload field
|
74
|
+
# @return [Array<String>] Strings representing binary ("01010101" etc) for each slice
|
75
|
+
def _bit_slices(start, length, chunk_size)
|
76
|
+
_access(start, length) { |bits| bits.chars.each_slice(chunk_size) }
|
77
|
+
end
|
78
|
+
|
79
|
+
# convert a 6b string but trim off the 0s ('@')
|
80
|
+
# @param start [Integer] The index of the first bit in the payload field
|
81
|
+
# @param length [Integer] The number of bits in the payload field
|
82
|
+
# @return [String]
|
83
|
+
def _6b_string_nullterminated(start, length)
|
84
|
+
_6b_string(start, length).split("@", 2)[0]
|
85
|
+
end
|
86
|
+
|
87
|
+
# directly convert a string to a binary number as you'd read it
|
88
|
+
# @param start [Integer] The index of the first bit in the payload field
|
89
|
+
# @param length [Integer] The number of bits in the payload field
|
90
|
+
# @param equiv_nil [Integer] If applicable, the value for this field that would indicate nil
|
91
|
+
# @return [Integer] an unsigned integer value
|
92
|
+
def _6b_unsigned_integer(start, length, equiv_nil = nil)
|
93
|
+
ret = _access(start, length) { |bits| bits.to_i(2) }
|
94
|
+
return nil if ret == equiv_nil
|
95
|
+
ret
|
96
|
+
end
|
97
|
+
|
98
|
+
# perform a twos complement operation on part of the payload
|
99
|
+
# @param start [Integer] The index of the first bit in the payload field
|
100
|
+
# @param length [Integer] The number of bits in the payload field
|
101
|
+
# @param equiv_nil [Integer] If applicable, the value for this field that would indicate nil
|
102
|
+
# @return [Integer] an integer value
|
103
|
+
def _6b_integer(start, length, equiv_nil = nil)
|
104
|
+
case @payload_bitstring[start]
|
105
|
+
when "0"
|
106
|
+
_6b_unsigned_integer(start, length, equiv_nil)
|
107
|
+
when "1"
|
108
|
+
# MSB is 1 for negative
|
109
|
+
# two's complement: flip bits, then add 1
|
110
|
+
ret = _access(start, length) { |bits| (bits.tr("01", "10").to_i(2) + 1) * -1 }
|
111
|
+
return nil if ret == equiv_nil
|
112
|
+
ret
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# scale an integer by dividing it by a denominator
|
117
|
+
# @param start [Integer] The index of the first bit in the payload field
|
118
|
+
# @param length [Integer] The number of bits in the payload field
|
119
|
+
# @param denominator [Integer] The divisor to use in scaling down the result
|
120
|
+
# @param equiv_nil [Integer] If applicable, the value for this field that would indicate nil
|
121
|
+
# @return [Integer] an integer value
|
122
|
+
def _6b_integer_scaled(start, length, denominator, equiv_nil = nil)
|
123
|
+
ret = _6b_integer(start, length, equiv_nil)
|
124
|
+
return nil if ret.nil?
|
125
|
+
ret.to_f / denominator
|
126
|
+
end
|
127
|
+
|
128
|
+
# scale an unsigned integer by dividing it by a denominator
|
129
|
+
# @param start [Integer] The index of the first bit in the payload field
|
130
|
+
# @param length [Integer] The number of bits in the payload field
|
131
|
+
# @param denominator [Integer] The divisor to use in scaling down the result
|
132
|
+
# @param equiv_nil [Integer] If applicable, the value for this field that would indicate nil
|
133
|
+
# @return [Integer] an integer value
|
134
|
+
def _6b_unsigned_integer_scaled(start, length, denominator, equiv_nil = nil)
|
135
|
+
ret = _6b_unsigned_integer(start, length, equiv_nil)
|
136
|
+
return nil if ret.nil?
|
137
|
+
ret.to_f / denominator
|
138
|
+
end
|
139
|
+
|
140
|
+
# scale an integer by dividing it by a denominator
|
141
|
+
# @param start [Integer] The index of the first bit in the payload field
|
142
|
+
# @param length [Integer] The number of bits in the payload field
|
143
|
+
# @param denominator [Integer] The divisor to use in scaling down the result
|
144
|
+
# @param shift [Float] the amount to shift (up) the result by
|
145
|
+
# @param equiv_nil [Integer] If applicable, the value for this field that would indicate nil
|
146
|
+
# @return [Integer] an integer value
|
147
|
+
def _6b_integer_scaled_shifted(start, length, denominator, shift, equiv_nil = nil)
|
148
|
+
ret = _6b_integer_scaled(start, length, denominator, equiv_nil)
|
149
|
+
return nil if ret.nil?
|
150
|
+
ret + shift
|
151
|
+
end
|
152
|
+
|
153
|
+
# scale an unsigned integer by dividing it by a denominator
|
154
|
+
# @param start [Integer] The index of the first bit in the payload field
|
155
|
+
# @param length [Integer] The number of bits in the payload field
|
156
|
+
# @param denominator [Integer] The divisor to use in scaling down the result
|
157
|
+
# @param shift [Float] the amount to shift (up) the result by
|
158
|
+
# @param equiv_nil [Integer] If applicable, the value for this field that would indicate nil
|
159
|
+
# @return [Integer] an integer value
|
160
|
+
def _6b_unsigned_integer_scaled_shifted(start, length, denominator, shift, equiv_nil = nil)
|
161
|
+
ret = _6b_unsigned_integer_scaled(start, length, denominator, equiv_nil)
|
162
|
+
return nil if ret.nil?
|
163
|
+
ret + shift
|
164
|
+
end
|
165
|
+
|
166
|
+
# Get the value of a bit in the payload
|
167
|
+
# @param start [Integer] The index of the first bit in the payload field
|
168
|
+
# @param _ [Integer] Doesn't matter. Here so signatures match; we hard-code 1 because it's 1 bit.
|
169
|
+
# @return [bool]
|
170
|
+
def _6b_boolean(start, _)
|
171
|
+
_access(start, 1) { |bits| bits.to_i == 1 }
|
172
|
+
end
|
173
|
+
|
174
|
+
# Return a string representing binary
|
175
|
+
# @param start [Integer] The index of the first bit in the payload field
|
176
|
+
# @param length [Integer] The number of bits in the payload field
|
177
|
+
# @return [String] e.g. "0101010101011000"
|
178
|
+
def _2b_data_string(start, length)
|
179
|
+
_access(start, length)
|
180
|
+
end
|
181
|
+
|
182
|
+
# use shorthand for data types as defined in http://catb.org/gpsd/AIVDM.html
|
183
|
+
alias_method :_u, :_6b_unsigned_integer
|
184
|
+
alias_method :_U, :_6b_unsigned_integer_scaled
|
185
|
+
alias_method :_i, :_6b_integer
|
186
|
+
alias_method :_I, :_6b_integer_scaled
|
187
|
+
alias_method :_b, :_6b_boolean
|
188
|
+
alias_method :_e, :_6b_unsigned_integer
|
189
|
+
alias_method :_t, :_6b_string_nullterminated
|
190
|
+
alias_method :_d, :_2b_data_string
|
191
|
+
alias_method :_UU, :_6b_unsigned_integer_scaled_shifted
|
192
|
+
alias_method :_II, :_6b_integer_scaled_shifted
|
193
|
+
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require_relative "payload"
|
1
2
|
|
2
3
|
module NMEAPlus
|
3
4
|
module Message
|
@@ -5,158 +6,12 @@ module NMEAPlus
|
|
5
6
|
# There are many VDM payload types, and this is their container. See {VDMMsg}.
|
6
7
|
module VDMPayload
|
7
8
|
# The base class for the AIS payload (of {NMEAPlus::Message::AIS::VDM}), which uses its own encoding for its own subtypes
|
8
|
-
class VDMMsg
|
9
|
-
|
10
|
-
def initialize; end
|
11
|
-
|
12
|
-
# @return [String] The raw "armored payload" in the original message
|
13
|
-
attr_accessor :payload_bitstring
|
14
|
-
|
15
|
-
# @return [Integer] The number of padding characters required to bring the payload to a 6 bit boundary
|
16
|
-
attr_accessor :fill_bits
|
17
|
-
|
18
|
-
# make our own shortcut syntax for payload attributes
|
19
|
-
# @param name [String] What the accessor will be called
|
20
|
-
# @param start_bit [Integer] The index of first bit of this field in the payload
|
21
|
-
# @param length [Integer] The number of bits in this field
|
22
|
-
# @param formatter [Symbol] The symbol for the formatting function to apply to the field (optional)
|
23
|
-
# @param formatter_arg Any argument necessary for the formatting function
|
24
|
-
# @macro [attach] payload_reader
|
25
|
-
# @!attribute [r] $1
|
26
|
-
# @return The field defined by $3 bits starting at bit $2 of the payload, formatted with the function {#$4}($5)
|
27
|
-
def self.payload_reader(name, start_bit, length, formatter, formatter_arg = nil)
|
28
|
-
if formatter_arg.nil?
|
29
|
-
self.class_eval("def #{name};#{formatter}(#{start_bit}, #{length});end")
|
30
|
-
else
|
31
|
-
self.class_eval("def #{name};#{formatter}(#{start_bit}, #{length}, #{formatter_arg});end")
|
32
|
-
end
|
33
|
-
end
|
9
|
+
class VDMMsg < NMEAPlus::Message::AIS::VDMPayload::Payload
|
34
10
|
|
35
11
|
payload_reader :message_type, 0, 6, :_u
|
36
12
|
payload_reader :repeat_indicator, 6, 2, :_u
|
37
13
|
payload_reader :source_mmsi, 8, 30, :_u
|
38
14
|
|
39
|
-
# Convert 6-bit ascii to a character, according to http://catb.org/gpsd/AIVDM.html#_ais_payload_data_types
|
40
|
-
# @param ord [Integer] The 6-bit ascii code
|
41
|
-
# @return [String] the character for that code
|
42
|
-
def _6b_ascii(ord)
|
43
|
-
'@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !"#$%&\'()*+,-./0123456789:;<=>?'[ord]
|
44
|
-
end
|
45
|
-
|
46
|
-
# Access part of the payload. If there aren't bytes, there, return nil
|
47
|
-
# else execute a block
|
48
|
-
# @param start [Integer] The index of the first bit in the payload field
|
49
|
-
# @param length [Integer] The number of bits in the payload field
|
50
|
-
# @return Nil or whatever is yielded by the block
|
51
|
-
# @yield [String] A binary coded string ("010010101" etc)
|
52
|
-
def _access(start, length)
|
53
|
-
part = @payload_bitstring[start, length]
|
54
|
-
return nil if part.nil? || part.empty?
|
55
|
-
yield part
|
56
|
-
end
|
57
|
-
|
58
|
-
# pull out 6b chunks from the payload, then convert those to their more familar characters
|
59
|
-
# @param start [Integer] The index of the first bit in the payload field
|
60
|
-
# @param length [Integer] The number of bits in the payload field
|
61
|
-
# @return [String]
|
62
|
-
def _6b_string(start, length)
|
63
|
-
_bit_slices(start, length, 6).to_a.map(&:join).map { |x| _6b_ascii(x.to_i(2)) }.join
|
64
|
-
end
|
65
|
-
|
66
|
-
# pull out 8b chunks from the payload, then convert those to their more familar characters
|
67
|
-
# @param start [Integer] The index of the first bit in the payload field
|
68
|
-
# @param length [Integer] The number of bits in the payload field
|
69
|
-
# @return [String]
|
70
|
-
def _8b_data_string(start, length)
|
71
|
-
_bit_slices(start, length, 8).to_a.map(&:join).map { |x| x.to_i(2).chr }.join
|
72
|
-
end
|
73
|
-
|
74
|
-
# Slice a part of the payload into binary chunks of a given size
|
75
|
-
# @param start [Integer] The index of the first bit in the payload field
|
76
|
-
# @param length [Integer] The number of bits in the payload field
|
77
|
-
# @return [Array<String>] Strings representing binary ("01010101" etc) for each slice
|
78
|
-
def _bit_slices(start, length, chunk_size)
|
79
|
-
_access(start, length) { |bits| bits.chars.each_slice(chunk_size) }
|
80
|
-
end
|
81
|
-
|
82
|
-
# convert a 6b string but trim off the 0s ('@')
|
83
|
-
# @param start [Integer] The index of the first bit in the payload field
|
84
|
-
# @param length [Integer] The number of bits in the payload field
|
85
|
-
# @return [String]
|
86
|
-
def _6b_string_nullterminated(start, length)
|
87
|
-
_6b_string(start, length).split("@", 2)[0]
|
88
|
-
end
|
89
|
-
|
90
|
-
# directly convert a string to a binary number as you'd read it
|
91
|
-
# @param start [Integer] The index of the first bit in the payload field
|
92
|
-
# @param length [Integer] The number of bits in the payload field
|
93
|
-
# @return [Integer] an unsigned integer value
|
94
|
-
def _6b_unsigned_integer(start, length)
|
95
|
-
_access(start, length) { |bits| bits.to_i(2) }
|
96
|
-
end
|
97
|
-
|
98
|
-
# perform a twos complement operation on part of the payload
|
99
|
-
# @param start [Integer] The index of the first bit in the payload field
|
100
|
-
# @param length [Integer] The number of bits in the payload field
|
101
|
-
# @return [Integer] an integer value
|
102
|
-
def _6b_twoscomplement(start, length)
|
103
|
-
# two's complement: flip bits, then add 1
|
104
|
-
_access(start, length) { |bits| bits.tr("01", "10").to_i(2) + 1 }
|
105
|
-
end
|
106
|
-
|
107
|
-
# @param start [Integer] The index of the first bit in the payload field
|
108
|
-
# @param length [Integer] The number of bits in the payload field
|
109
|
-
# @return [Integer] an integer value
|
110
|
-
def _6b_integer(start, length)
|
111
|
-
# MSB is 1 for negative
|
112
|
-
twoc = _6b_twoscomplement(start, length)
|
113
|
-
twoc && twoc * (@payload_bitstring[start] == 0 ? 1 : -1)
|
114
|
-
end
|
115
|
-
|
116
|
-
# scale an integer by dividing it by 10^decimal_places
|
117
|
-
# @param start [Integer] The index of the first bit in the payload field
|
118
|
-
# @param length [Integer] The number of bits in the payload field
|
119
|
-
# @param decimal_places [Integer] The power of ten to use in scaling down the result
|
120
|
-
# @return [Integer] an integer value
|
121
|
-
def _6b_integer_scaled(start, length, decimal_places)
|
122
|
-
_6b_integer(start, length).to_f / (10 ** decimal_places)
|
123
|
-
end
|
124
|
-
|
125
|
-
# scale an unsigned integer by dividing it by 10^decimal_places
|
126
|
-
# @param start [Integer] The index of the first bit in the payload field
|
127
|
-
# @param length [Integer] The number of bits in the payload field
|
128
|
-
# @param decimal_places [Integer] The power of ten to use in scaling the result
|
129
|
-
# @return [Integer] an integer value
|
130
|
-
def _6b_unsigned_integer_scaled(start, length, decimal_places)
|
131
|
-
_6b_unsigned_integer(start, length).to_f / (10.0 ** decimal_places)
|
132
|
-
end
|
133
|
-
|
134
|
-
# Get the value of a bit in the payload
|
135
|
-
# @param start [Integer] The index of the first bit in the payload field
|
136
|
-
# @param _ [Integer] Doesn't matter. Here so signatures match; we hard-code 1 because it's 1 bit.
|
137
|
-
# @return [bool]
|
138
|
-
def _6b_boolean(start, _)
|
139
|
-
_access(start, 1) { |bits| bits.to_i == 1 }
|
140
|
-
end
|
141
|
-
|
142
|
-
# Return a string representing binary
|
143
|
-
# @param start [Integer] The index of the first bit in the payload field
|
144
|
-
# @param length [Integer] The number of bits in the payload field
|
145
|
-
# @return [String] e.g. "0101010101011000"
|
146
|
-
def _2b_data_string(start, length)
|
147
|
-
_access(start, length)
|
148
|
-
end
|
149
|
-
|
150
|
-
# use shorthand for data types as defined in http://catb.org/gpsd/AIVDM.html
|
151
|
-
alias_method :_u, :_6b_unsigned_integer
|
152
|
-
alias_method :_U, :_6b_unsigned_integer_scaled
|
153
|
-
alias_method :_i, :_6b_integer
|
154
|
-
alias_method :_I, :_6b_integer_scaled
|
155
|
-
alias_method :_b, :_6b_boolean
|
156
|
-
alias_method :_e, :_6b_unsigned_integer
|
157
|
-
alias_method :_t, :_6b_string_nullterminated
|
158
|
-
alias_method :_d, :_2b_data_string
|
159
|
-
|
160
15
|
# The ship cargo type description lookup table
|
161
16
|
# @param code [Integer] The cargo type id
|
162
17
|
# @return [String] Cargo type description
|
@@ -231,6 +86,25 @@ module NMEAPlus
|
|
231
86
|
980_000_000 < source_mmsi && source_mmsi < 990_000_000
|
232
87
|
end
|
233
88
|
|
89
|
+
# @param code [Integer] The navigational status id
|
90
|
+
# @return [String] Navigational status description
|
91
|
+
def get_navigational_status_description(code)
|
92
|
+
return nil if code.nil?
|
93
|
+
case code
|
94
|
+
when 0 then return "Under way using engine"
|
95
|
+
when 1 then return "At anchor"
|
96
|
+
when 2 then return "Not under command"
|
97
|
+
when 3 then return "Restricted manoeuverability"
|
98
|
+
when 4 then return "Constrained by her draught"
|
99
|
+
when 5 then return "Moored"
|
100
|
+
when 6 then return "Aground"
|
101
|
+
when 7 then return "Engaged in Fishing"
|
102
|
+
when 8 then return "Under way sailing"
|
103
|
+
when 14 then return "AIS-SART active"
|
104
|
+
end
|
105
|
+
"Reserved for future use"
|
106
|
+
end
|
107
|
+
|
234
108
|
end
|
235
109
|
|
236
110
|
# We haven't defined all the AIS payload types, so this is a catch-all
|