nmea_plus 1.0.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/gem/lib/nmea_plus.rb +63 -0
- data/gem/lib/nmea_plus/ais_message_factory.rb +17 -0
- data/gem/lib/nmea_plus/generated_parser/parser.rb +127 -0
- data/gem/lib/nmea_plus/generated_parser/tokenizer.rb +100 -0
- data/gem/lib/nmea_plus/message/ais/base_ais.rb +20 -0
- data/gem/lib/nmea_plus/message/ais/vdm.rb +100 -0
- data/gem/lib/nmea_plus/message/ais/vdm_payload/vdm_msg.rb +99 -0
- data/gem/lib/nmea_plus/message/ais/vdm_payload/vdm_msg1.rb +65 -0
- data/gem/lib/nmea_plus/message/ais/vdm_payload/vdm_msg5.rb +38 -0
- data/gem/lib/nmea_plus/message/ais/vdm_payload/vdm_msg8.rb +43 -0
- data/gem/lib/nmea_plus/message/base.rb +172 -0
- data/gem/lib/nmea_plus/message/nmea/aam.rb +17 -0
- data/gem/lib/nmea_plus/message/nmea/alm.rb +25 -0
- data/gem/lib/nmea_plus/message/nmea/apa.rb +20 -0
- data/gem/lib/nmea_plus/message/nmea/apb.rb +14 -0
- data/gem/lib/nmea_plus/message/nmea/base_nmea.rb +36 -0
- data/gem/lib/nmea_plus/message/nmea/bod.rb +14 -0
- data/gem/lib/nmea_plus/message/nmea/bwc.rb +12 -0
- data/gem/lib/nmea_plus/message/nmea/bwr.rb +25 -0
- data/gem/lib/nmea_plus/message/nmea/bww.rb +11 -0
- data/gem/lib/nmea_plus/message/nmea/dbk.rb +13 -0
- data/gem/lib/nmea_plus/message/nmea/dbs.rb +11 -0
- data/gem/lib/nmea_plus/message/nmea/dbt.rb +11 -0
- data/gem/lib/nmea_plus/message/nmea/dcn.rb +26 -0
- data/gem/lib/nmea_plus/message/nmea/dpt.rb +12 -0
- data/gem/lib/nmea_plus/message/nmea/dtm.rb +24 -0
- data/gem/lib/nmea_plus/message/nmea/fsi.rb +14 -0
- data/gem/lib/nmea_plus/message/nmea/gbs.rb +18 -0
- data/gem/lib/nmea_plus/message/nmea/gga.rb +32 -0
- data/gem/lib/nmea_plus/message/nmea/glc.rb +23 -0
- data/gem/lib/nmea_plus/message/nmea/gll.rb +21 -0
- data/gem/lib/nmea_plus/message/nmea/gns.rb +28 -0
- data/gem/lib/nmea_plus/message/nmea/grs.rb +24 -0
- data/gem/lib/nmea_plus/message/nmea/gsa.rb +27 -0
- data/gem/lib/nmea_plus/message/nmea/gst.rb +18 -0
- data/gem/lib/nmea_plus/message/nmea/gsv.rb +29 -0
- data/gem/lib/nmea_plus/message/nmea/gtd.rb +15 -0
- data/gem/lib/nmea_plus/message/nmea/gxa.rb +22 -0
- data/gem/lib/nmea_plus/message/nmea/hdg.rb +20 -0
- data/gem/lib/nmea_plus/message/nmea/hdm.rb +11 -0
- data/gem/lib/nmea_plus/message/nmea/hdt.rb +11 -0
- data/gem/lib/nmea_plus/message/nmea/hfb.rb +12 -0
- data/gem/lib/nmea_plus/message/nmea/hsc.rb +12 -0
- data/gem/lib/nmea_plus/message/nmea/its.rb +11 -0
- data/gem/lib/nmea_plus/message/nmea/lcd.rb +23 -0
- data/gem/lib/nmea_plus/message/nmea/msk.rb +15 -0
- data/gem/lib/nmea_plus/message/nmea/mss.rb +15 -0
- data/gem/lib/nmea_plus/message/nmea/mtw.rb +12 -0
- data/gem/lib/nmea_plus/message/nmea/mwv.rb +15 -0
- data/gem/lib/nmea_plus/message/nmea/oln.rb +22 -0
- data/gem/lib/nmea_plus/message/nmea/osd.rb +19 -0
- data/gem/lib/nmea_plus/message/nmea/pashr.rb +21 -0
- data/gem/lib/nmea_plus/message/nmea/r00.rb +11 -0
- data/gem/lib/nmea_plus/message/nmea/rma.rb +30 -0
- data/gem/lib/nmea_plus/message/nmea/rmb.rb +30 -0
- data/gem/lib/nmea_plus/message/nmea/rmc.rb +33 -0
- data/gem/lib/nmea_plus/message/nmea/rot.rb +12 -0
- data/gem/lib/nmea_plus/message/nmea/rpm.rb +15 -0
- data/gem/lib/nmea_plus/message/nmea/rsa.rb +17 -0
- data/gem/lib/nmea_plus/message/nmea/rsd.rb +14 -0
- data/gem/lib/nmea_plus/message/nmea/rte.rb +16 -0
- data/gem/lib/nmea_plus/message/nmea/sfi.rb +18 -0
- data/gem/lib/nmea_plus/message/nmea/stn.rb +11 -0
- data/gem/lib/nmea_plus/message/nmea/tds.rb +11 -0
- data/gem/lib/nmea_plus/message/nmea/tfi.rb +13 -0
- data/gem/lib/nmea_plus/message/nmea/tpc.rb +13 -0
- data/gem/lib/nmea_plus/message/nmea/tpr.rb +13 -0
- data/gem/lib/nmea_plus/message/nmea/tpt.rb +13 -0
- data/gem/lib/nmea_plus/message/nmea/trf.rb +28 -0
- data/gem/lib/nmea_plus/message/nmea/ttm.rb +22 -0
- data/gem/lib/nmea_plus/message/nmea/vbw.rb +16 -0
- data/gem/lib/nmea_plus/message/nmea/vdr.rb +13 -0
- data/gem/lib/nmea_plus/message/nmea/vhw.rb +14 -0
- data/gem/lib/nmea_plus/message/nmea/vlw.rb +12 -0
- data/gem/lib/nmea_plus/message/nmea/vpw.rb +12 -0
- data/gem/lib/nmea_plus/message/nmea/vtg.rb +41 -0
- data/gem/lib/nmea_plus/message/nmea/vwr.rb +15 -0
- data/gem/lib/nmea_plus/message/nmea/wcv.rb +12 -0
- data/gem/lib/nmea_plus/message/nmea/wnc.rb +14 -0
- data/gem/lib/nmea_plus/message/nmea/wpl.rb +19 -0
- data/gem/lib/nmea_plus/message/nmea/xdr.rb +14 -0
- data/gem/lib/nmea_plus/message/nmea/xte.rb +16 -0
- data/gem/lib/nmea_plus/message/nmea/xtr.rb +12 -0
- data/gem/lib/nmea_plus/message/nmea/zda.rb +23 -0
- data/gem/lib/nmea_plus/message/nmea/zfo.rb +13 -0
- data/gem/lib/nmea_plus/message/nmea/ztg.rb +13 -0
- data/gem/lib/nmea_plus/message_factory.rb +68 -0
- data/gem/lib/nmea_plus/nmea_message_factory.rb +110 -0
- data/gem/lib/nmea_plus/version.rb +3 -0
- metadata +285 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: cb6a15ea1c4b8d040614ae117703221df082cf53
|
|
4
|
+
data.tar.gz: 3d738a04a524c69e02c371befc3d1edd9f913a96
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: f026b1f759c56c97952ea97b3b7a6190c0930e4cb02486a715cf704eefe5fa8c4f012c19b6b8778dd32be709be95cc671c23f267096635ddcd55d3f2e1d1074c
|
|
7
|
+
data.tar.gz: 9002792450538ed8f492db5f54707f1631bc44f27acede7a44868b82900755177da03974922565249ebde61b175fd56023aa5cf23878d0141b606dbad891ceb8
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require 'nmea_plus/version'
|
|
2
|
+
|
|
3
|
+
require 'nmea_plus/generated_parser/parser'
|
|
4
|
+
require 'nmea_plus/generated_parser/tokenizer'
|
|
5
|
+
|
|
6
|
+
module NMEAPlus
|
|
7
|
+
class SourceDecoder
|
|
8
|
+
attr_accessor :throw_on_parse_fail
|
|
9
|
+
attr_accessor :throw_on_unrecognized_type # typically for development
|
|
10
|
+
|
|
11
|
+
def initialize(line_reader)
|
|
12
|
+
unless line_reader.respond_to? :each_line
|
|
13
|
+
fail ArgumentError, "line_reader must inherit from type IO (or implement each_line)"
|
|
14
|
+
end
|
|
15
|
+
@throw_on_parse_fail = false
|
|
16
|
+
@source = line_reader
|
|
17
|
+
@decoder = NMEAPlus::Decoder.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# return each parsed message
|
|
21
|
+
def each_message
|
|
22
|
+
@source.each_line do |line|
|
|
23
|
+
if @throw_on_parse_fail
|
|
24
|
+
yield @decoder.parse(line)
|
|
25
|
+
else
|
|
26
|
+
begin
|
|
27
|
+
y = @decoder.parse(line)
|
|
28
|
+
yield y
|
|
29
|
+
rescue
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# return messages grouped into multipart chains as required
|
|
36
|
+
def each_complete_message
|
|
37
|
+
partials = {}
|
|
38
|
+
each_message do |msg|
|
|
39
|
+
slot = msg.data_type
|
|
40
|
+
|
|
41
|
+
if partials[slot].nil?
|
|
42
|
+
partials[slot] = msg
|
|
43
|
+
else
|
|
44
|
+
# the message was already in there
|
|
45
|
+
if 1 != (msg.message_number - partials[slot].message_number)
|
|
46
|
+
# error! just overwrite what was there
|
|
47
|
+
partials[slot] = msg
|
|
48
|
+
else
|
|
49
|
+
partials[slot].add_message_part(msg)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# take action if we've completed the chain
|
|
54
|
+
maybe_full = partials[slot]
|
|
55
|
+
if maybe_full.all_messages_received?
|
|
56
|
+
partials[slot] = nil
|
|
57
|
+
yield maybe_full
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
require_relative 'message_factory'
|
|
3
|
+
|
|
4
|
+
require_relative 'message/ais/vdm'
|
|
5
|
+
|
|
6
|
+
module NMEAPlus
|
|
7
|
+
class AISMessageFactory < MessageFactory
|
|
8
|
+
def self.parent_module
|
|
9
|
+
"AIS"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.alternate_data_type(data_type)
|
|
13
|
+
# match last 3 digits (get rid of talker)
|
|
14
|
+
data_type[2..4]
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#
|
|
2
|
+
# DO NOT MODIFY!!!!
|
|
3
|
+
# This file is automatically generated by Racc 1.4.13
|
|
4
|
+
# from Racc grammer file "".
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
require 'racc/parser.rb'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
require_relative "../nmea_message_factory"
|
|
11
|
+
require_relative "../ais_message_factory"
|
|
12
|
+
|
|
13
|
+
module NMEAPlus
|
|
14
|
+
class Parser < Racc::Parser
|
|
15
|
+
|
|
16
|
+
module_eval(<<'...end parser.y/module_eval...', 'parser.y', 19)
|
|
17
|
+
|
|
18
|
+
# override racc's on_error so we can have context in our error messages
|
|
19
|
+
def on_error(t, val, vstack)
|
|
20
|
+
errcontext = (@ss.pre_match[-10..-1] || @ss.pre_match) +
|
|
21
|
+
@ss.matched + @ss.post_match[0..9]
|
|
22
|
+
line_number = @ss.pre_match.lines.count
|
|
23
|
+
raise ParseError, sprintf("parse error on value %s (%s) " +
|
|
24
|
+
"on line %s around \"%s\"",
|
|
25
|
+
val.inspect, token_to_str(t) || '?',
|
|
26
|
+
line_number, errcontext)
|
|
27
|
+
end
|
|
28
|
+
...end parser.y/module_eval...
|
|
29
|
+
##### State transition tables begin ###
|
|
30
|
+
|
|
31
|
+
racc_action_table = [
|
|
32
|
+
2, 3, 6, 5, 4, 7, 8, 9 ]
|
|
33
|
+
|
|
34
|
+
racc_action_check = [
|
|
35
|
+
0, 0, 3, 2, 1, 4, 5, 6 ]
|
|
36
|
+
|
|
37
|
+
racc_action_pointer = [
|
|
38
|
+
-3, 4, -2, -3, 5, 4, 5, nil, nil, nil ]
|
|
39
|
+
|
|
40
|
+
racc_action_default = [
|
|
41
|
+
-3, -3, -3, -3, -3, -3, -3, 10, -1, -2 ]
|
|
42
|
+
|
|
43
|
+
racc_goto_table = [
|
|
44
|
+
1 ]
|
|
45
|
+
|
|
46
|
+
racc_goto_check = [
|
|
47
|
+
1 ]
|
|
48
|
+
|
|
49
|
+
racc_goto_pointer = [
|
|
50
|
+
nil, 0 ]
|
|
51
|
+
|
|
52
|
+
racc_goto_default = [
|
|
53
|
+
nil, nil ]
|
|
54
|
+
|
|
55
|
+
racc_reduce_table = [
|
|
56
|
+
0, 0, :racc_error,
|
|
57
|
+
3, 7, :_reduce_1,
|
|
58
|
+
3, 7, :_reduce_2 ]
|
|
59
|
+
|
|
60
|
+
racc_reduce_n = 3
|
|
61
|
+
|
|
62
|
+
racc_shift_n = 10
|
|
63
|
+
|
|
64
|
+
racc_token_table = {
|
|
65
|
+
false => 0,
|
|
66
|
+
:error => 1,
|
|
67
|
+
:CSUM => 2,
|
|
68
|
+
:CASH => 3,
|
|
69
|
+
:BANG => 4,
|
|
70
|
+
:DATA => 5 }
|
|
71
|
+
|
|
72
|
+
racc_nt_base = 6
|
|
73
|
+
|
|
74
|
+
racc_use_result_var = true
|
|
75
|
+
|
|
76
|
+
Racc_arg = [
|
|
77
|
+
racc_action_table,
|
|
78
|
+
racc_action_check,
|
|
79
|
+
racc_action_default,
|
|
80
|
+
racc_action_pointer,
|
|
81
|
+
racc_goto_table,
|
|
82
|
+
racc_goto_check,
|
|
83
|
+
racc_goto_default,
|
|
84
|
+
racc_goto_pointer,
|
|
85
|
+
racc_nt_base,
|
|
86
|
+
racc_reduce_table,
|
|
87
|
+
racc_token_table,
|
|
88
|
+
racc_shift_n,
|
|
89
|
+
racc_reduce_n,
|
|
90
|
+
racc_use_result_var ]
|
|
91
|
+
|
|
92
|
+
Racc_token_to_s_table = [
|
|
93
|
+
"$end",
|
|
94
|
+
"error",
|
|
95
|
+
"CSUM",
|
|
96
|
+
"CASH",
|
|
97
|
+
"BANG",
|
|
98
|
+
"DATA",
|
|
99
|
+
"$start",
|
|
100
|
+
"start" ]
|
|
101
|
+
|
|
102
|
+
Racc_debug_parser = false
|
|
103
|
+
|
|
104
|
+
##### State transition tables end #####
|
|
105
|
+
|
|
106
|
+
# reduce 0 omitted
|
|
107
|
+
|
|
108
|
+
module_eval(<<'.,.,', 'parser.y', 8)
|
|
109
|
+
def _reduce_1(val, _values, result)
|
|
110
|
+
result = NMEAPlus::NMEAMessageFactory.create(val[0], val[1], val[2])
|
|
111
|
+
result
|
|
112
|
+
end
|
|
113
|
+
.,.,
|
|
114
|
+
|
|
115
|
+
module_eval(<<'.,.,', 'parser.y', 9)
|
|
116
|
+
def _reduce_2(val, _values, result)
|
|
117
|
+
result = NMEAPlus::AISMessageFactory.create(val[0], val[1], val[2])
|
|
118
|
+
result
|
|
119
|
+
end
|
|
120
|
+
.,.,
|
|
121
|
+
|
|
122
|
+
def _reduce_none(val, _values, result)
|
|
123
|
+
val[0]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
end # class Parser
|
|
127
|
+
end # module NMEAPlus
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# DO NOT MODIFY!!!!
|
|
3
|
+
# This file is automatically generated by rex 1.0.5
|
|
4
|
+
# from lexical definition file "/Users/iakatz/Code Base/nmea_plus/parser/tokenizer.rex".
|
|
5
|
+
#++
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
module NMEAPlus
|
|
9
|
+
class Decoder < Parser # not indented due to constraints of .rex format
|
|
10
|
+
require 'strscan'
|
|
11
|
+
|
|
12
|
+
class ScanError < StandardError ; end
|
|
13
|
+
|
|
14
|
+
attr_reader :lineno
|
|
15
|
+
attr_reader :filename
|
|
16
|
+
attr_accessor :state
|
|
17
|
+
|
|
18
|
+
def scan_setup(str)
|
|
19
|
+
@ss = StringScanner.new(str)
|
|
20
|
+
@lineno = 1
|
|
21
|
+
@state = nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def action
|
|
25
|
+
yield
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def scan_str(str)
|
|
29
|
+
scan_setup(str)
|
|
30
|
+
do_parse
|
|
31
|
+
end
|
|
32
|
+
alias :scan :scan_str
|
|
33
|
+
|
|
34
|
+
def load_file( filename )
|
|
35
|
+
@filename = filename
|
|
36
|
+
open(filename, "r") do |f|
|
|
37
|
+
scan_setup(f.read)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def scan_file( filename )
|
|
42
|
+
load_file(filename)
|
|
43
|
+
do_parse
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def next_token
|
|
48
|
+
return if @ss.eos?
|
|
49
|
+
|
|
50
|
+
# skips empty actions
|
|
51
|
+
until token = _next_token or @ss.eos?; end
|
|
52
|
+
token
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def _next_token
|
|
56
|
+
text = @ss.peek(1)
|
|
57
|
+
@lineno += 1 if text == "\n"
|
|
58
|
+
token = case @state
|
|
59
|
+
when nil
|
|
60
|
+
case
|
|
61
|
+
when (text = @ss.scan(/\*[0-9A-F]{2}[\w\n\r]*/i))
|
|
62
|
+
action { [:CSUM, text[1..2]] }
|
|
63
|
+
|
|
64
|
+
when (text = @ss.scan(/\$/i))
|
|
65
|
+
action { [:CASH, text] }
|
|
66
|
+
|
|
67
|
+
when (text = @ss.scan(/!/i))
|
|
68
|
+
action { [:BANG, text] }
|
|
69
|
+
|
|
70
|
+
when (text = @ss.scan(/[^\*]+/i))
|
|
71
|
+
action { [:DATA, text] }
|
|
72
|
+
|
|
73
|
+
else
|
|
74
|
+
text = @ss.string[@ss.pos .. -1]
|
|
75
|
+
raise ScanError, "can not match: '" + text + "'"
|
|
76
|
+
end # if
|
|
77
|
+
|
|
78
|
+
else
|
|
79
|
+
raise ScanError, "undefined state: '" + state.to_s + "'"
|
|
80
|
+
end # case state
|
|
81
|
+
token
|
|
82
|
+
end # def _next_token
|
|
83
|
+
|
|
84
|
+
def parse(input)
|
|
85
|
+
@yydebug = true if ENV['DEBUG_RACC']
|
|
86
|
+
scan_str(input)
|
|
87
|
+
end
|
|
88
|
+
def tokenize(input)
|
|
89
|
+
scan_setup(input)
|
|
90
|
+
ret = []
|
|
91
|
+
last_token = nil
|
|
92
|
+
loop do
|
|
93
|
+
last_token = next_token
|
|
94
|
+
break if last_token.nil?
|
|
95
|
+
ret << last_token
|
|
96
|
+
end
|
|
97
|
+
ret
|
|
98
|
+
end
|
|
99
|
+
end # class
|
|
100
|
+
end # class
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
require_relative "../base"
|
|
3
|
+
|
|
4
|
+
module NMEAPlus
|
|
5
|
+
module Message
|
|
6
|
+
module AIS
|
|
7
|
+
|
|
8
|
+
class AISMessage < NMEAPlus::Message::Base
|
|
9
|
+
def talker
|
|
10
|
+
data_type[0..1]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def message_type
|
|
14
|
+
data_type[2..-1]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
require_relative "base_ais"
|
|
2
|
+
|
|
3
|
+
require_relative "vdm_payload/vdm_msg1"
|
|
4
|
+
require_relative "vdm_payload/vdm_msg5"
|
|
5
|
+
require_relative "vdm_payload/vdm_msg8"
|
|
6
|
+
|
|
7
|
+
=begin boilerplate for vdm payload objects
|
|
8
|
+
require_relative 'vdm_msg'
|
|
9
|
+
|
|
10
|
+
module NMEAPlus
|
|
11
|
+
module Message
|
|
12
|
+
module AIS
|
|
13
|
+
module VDMPayload
|
|
14
|
+
class VDMMsg5 < NMEAPlus::Message::AIS::VDMPayload::VDMMsg
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
=end
|
|
23
|
+
|
|
24
|
+
module NMEAPlus
|
|
25
|
+
module Message
|
|
26
|
+
module AIS
|
|
27
|
+
class VDM < NMEAPlus::Message::AIS::AISMessage
|
|
28
|
+
field_reader :total_messages, 1, :_integer
|
|
29
|
+
field_reader :message_number, 2, :_integer
|
|
30
|
+
field_reader :message_id, 3, :_string
|
|
31
|
+
field_reader :channel_code, 4, :_string
|
|
32
|
+
field_reader :raw_ais_payload, 5, :_string
|
|
33
|
+
field_reader :ais_payload_fill_bits, 6, :_integer
|
|
34
|
+
|
|
35
|
+
def ais
|
|
36
|
+
# factory method: find the appropriate message type class and instantiate it
|
|
37
|
+
p = full_dearmored_ais_payload
|
|
38
|
+
ret = _payload_container(p[0, 6].to_i(2))
|
|
39
|
+
ret.payload_bitstring = p
|
|
40
|
+
ret.fill_bits = last_ais_fill_bits
|
|
41
|
+
ret
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# the full encoded payload as it was received
|
|
45
|
+
def full_armored_ais_payload
|
|
46
|
+
# get the full message and fill bits for the last one
|
|
47
|
+
ptr = self
|
|
48
|
+
ret = ""
|
|
49
|
+
loop do
|
|
50
|
+
ret << ptr.raw_ais_payload
|
|
51
|
+
break if ptr.next_part.nil?
|
|
52
|
+
ptr = ptr.next_part
|
|
53
|
+
end
|
|
54
|
+
ret
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# a binary string ("0010101110110") representing the dearmored payload
|
|
58
|
+
def full_dearmored_ais_payload
|
|
59
|
+
data = full_armored_ais_payload
|
|
60
|
+
out = ""
|
|
61
|
+
# dearmor all but the last byte, then apply the fill bits to the last byte
|
|
62
|
+
data[0..-2].each_char { |c| out << _dearmor6b(c) }
|
|
63
|
+
out << _dearmor6b(data[-1], 6 - last_ais_fill_bits)
|
|
64
|
+
out
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def last_ais_fill_bits
|
|
68
|
+
# get the fill bits for the last message in the sequence
|
|
69
|
+
ptr = self
|
|
70
|
+
fill_bits = nil
|
|
71
|
+
loop do
|
|
72
|
+
fill_bits = ptr.ais_payload_fill_bits
|
|
73
|
+
break if ptr.next_part.nil?
|
|
74
|
+
ptr = ptr.next_part
|
|
75
|
+
end
|
|
76
|
+
fill_bits.to_i
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# perform the 6-bit to 8-bit conversion defined in the spec
|
|
80
|
+
def _dearmor6b(c, len = 6)
|
|
81
|
+
val = c.ord
|
|
82
|
+
if val >= 96
|
|
83
|
+
ret = val - 56
|
|
84
|
+
else
|
|
85
|
+
ret = val - 48
|
|
86
|
+
end
|
|
87
|
+
ret.to_s(2).rjust(6, "0")[0..(len - 1)]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def _payload_container(message_type_id)
|
|
91
|
+
class_identifier = "NMEAPlus::Message::AIS::VDMPayload::VDMMsg#{message_type_id}"
|
|
92
|
+
Object::const_get(class_identifier).new
|
|
93
|
+
rescue ::NameError
|
|
94
|
+
class_identifier = "NMEAPlus::Message::AIS::VDMPayload::VDMMsgUndefined" # generic
|
|
95
|
+
Object::const_get(class_identifier).new
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
class Class
|
|
2
|
+
# make our own shortcut syntax for payload attributes
|
|
3
|
+
def payload_reader(name, start_bit, length, formatter, formatter_arg = nil)
|
|
4
|
+
if formatter_arg.nil?
|
|
5
|
+
self.class_eval("def #{name};#{formatter}(#{start_bit}, #{length});end")
|
|
6
|
+
else
|
|
7
|
+
self.class_eval("def #{name};#{formatter}(#{start_bit}, #{length}, #{formatter_arg});end")
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module NMEAPlus
|
|
13
|
+
module Message
|
|
14
|
+
module AIS
|
|
15
|
+
module VDMPayload
|
|
16
|
+
class VDMMsg
|
|
17
|
+
attr_accessor :payload_bitstring
|
|
18
|
+
attr_accessor :fill_bits
|
|
19
|
+
|
|
20
|
+
payload_reader :message_type, 0, 6, :_u
|
|
21
|
+
payload_reader :repeat_indicator, 6, 2, :_u
|
|
22
|
+
payload_reader :source_mmsi, 8, 30, :_u
|
|
23
|
+
|
|
24
|
+
# lookup table for 6-bit ascii
|
|
25
|
+
def _6b_ascii(ord)
|
|
26
|
+
'@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !"#$%&\'()*+,-./0123456789:;<=>?'[ord]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# convert an entire string from the payload
|
|
30
|
+
def _6b_string(start, length)
|
|
31
|
+
# pull out 6b chunks from the string, use their value as a lookup into the ascii array
|
|
32
|
+
_bit_slices(start, length, 6).to_a.map(&:join).map { |x| _6b_ascii(x.to_i(2)) }.join
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def _8b_data_string(start, length)
|
|
36
|
+
_bit_slices(start, length, 8).to_a.map(&:join).map { |x| x.to_i(2).chr }.join
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def _bit_slices(start, length, chunk_size)
|
|
40
|
+
@payload_bitstring[start, length].chars.each_slice(chunk_size)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# convert a string but trim off the 0s ('@')
|
|
44
|
+
def _6b_string_nullterminated(start, length)
|
|
45
|
+
_6b_string(start, length).split("@", 2)[0]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# directly convert a string to a binary number as you'd read it
|
|
49
|
+
def _6b_unsigned_integer(start, length)
|
|
50
|
+
@payload_bitstring[start, length].to_i(2)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# perform a twos complement operation on part of the payload
|
|
54
|
+
def _6b_twoscomplement(start, length)
|
|
55
|
+
# two's complement: flip bits, then add 1
|
|
56
|
+
@payload_bitstring[start, length].tr("01", "10").to_i(2) + 1
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def _6b_integer(start, length)
|
|
60
|
+
# MSB is 1 for negative
|
|
61
|
+
_6b_twoscomplement(start, length) * (@payload_bitstring[start] == 0 ? 1 : -1)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# scale an integer by dividing it by 10^decimal_places
|
|
65
|
+
def _6b_integer_scaled(start, length, decimal_places)
|
|
66
|
+
_6b_integer(start, length).to_f / (10 ** decimal_places)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# scale an unsigned integer by dividing it by 10^decimal_places
|
|
70
|
+
def _6b_unsigned_integer_scaled(start, length, decimal_places)
|
|
71
|
+
_6b_unsigned_integer(start, length).to_f / (10.0 ** decimal_places)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def _6b_boolean(start, _)
|
|
75
|
+
@payload_bitstring[start].to_i == 1
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def _2b_data_string(start, length)
|
|
79
|
+
@payload_bitstring[start, length]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# use shorthand for data types as defined in http://catb.org/gpsd/AIVDM.html
|
|
83
|
+
alias_method :_u, :_6b_unsigned_integer
|
|
84
|
+
alias_method :_U, :_6b_unsigned_integer_scaled
|
|
85
|
+
alias_method :_i, :_6b_integer
|
|
86
|
+
alias_method :_I, :_6b_integer_scaled
|
|
87
|
+
alias_method :_b, :_6b_boolean
|
|
88
|
+
alias_method :_e, :_6b_unsigned_integer
|
|
89
|
+
alias_method :_t, :_6b_string_nullterminated
|
|
90
|
+
alias_method :_d, :_2b_data_string
|
|
91
|
+
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
class VDMMsgUndefined < VDMMsg; end
|
|
95
|
+
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|