nmea_plus 1.0.7 → 1.0.8
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 +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
|
[](https://rubygems.org/gems/nmea_plus)
|
|
4
4
|
[](https://travis-ci.org/ifreecarve/nmea_plus)
|
|
5
|
-
[](http://www.rubydoc.info/gems/nmea_plus/1.0.
|
|
5
|
+
[](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
|