nmea_plus 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +116 -0
- data/{gem/lib → lib}/nmea_plus.rb +19 -3
- data/lib/nmea_plus/ais_message_factory.rb +23 -0
- data/{gem/lib → lib}/nmea_plus/generated_parser/parser.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/generated_parser/tokenizer.rb +1 -1
- data/{gem/lib → lib}/nmea_plus/message/ais/base_ais.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/ais/vdm.rb +5 -4
- data/{gem/lib → lib}/nmea_plus/message/ais/vdm_payload/vdm_msg.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/ais/vdm_payload/vdm_msg1.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/ais/vdm_payload/vdm_msg5.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/ais/vdm_payload/vdm_msg8.rb +0 -0
- data/lib/nmea_plus/message/base.rb +235 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/aam.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/alm.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/apa.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/apb.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/base_nmea.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/bod.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/bwc.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/bwr.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/bww.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/dbk.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/dbs.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/dbt.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/dcn.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/dpt.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/dtm.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/fsi.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/gbs.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/gga.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/glc.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/gll.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/gns.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/grs.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/gsa.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/gst.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/gsv.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/gtd.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/gxa.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/hdg.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/hdm.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/hdt.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/hfb.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/hsc.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/its.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/lcd.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/msk.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/mss.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/mtw.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/mwv.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/oln.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/osd.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/pashr.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/r00.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/rma.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/rmb.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/rmc.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/rot.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/rpm.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/rsa.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/rsd.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/rte.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/sfi.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/stn.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/tds.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/tfi.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/tpc.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/tpr.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/tpt.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/trf.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/ttm.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/vbw.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/vdr.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/vhw.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/vlw.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/vpw.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/vtg.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/vwr.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/wcv.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/wnc.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/wpl.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/xdr.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/xte.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/xtr.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/zda.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/zfo.rb +0 -0
- data/{gem/lib → lib}/nmea_plus/message/nmea/ztg.rb +0 -0
- data/lib/nmea_plus/message_factory.rb +92 -0
- data/{gem/lib → lib}/nmea_plus/nmea_message_factory.rb +5 -0
- data/{gem/lib → lib}/nmea_plus/version.rb +1 -1
- metadata +117 -96
- data/gem/lib/nmea_plus/ais_message_factory.rb +0 -17
- data/gem/lib/nmea_plus/message/base.rb +0 -172
- data/gem/lib/nmea_plus/message_factory.rb +0 -68
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 14b78eb70f5197e702eda48e3b297cfa9e6aed78
|
4
|
+
data.tar.gz: cc2f08b71f5841d8436f2e53baca38745aa67169
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f2db86ca89249ba01692b2cd4251eede675e1550defbb0bb80b486fad391e6ed2b1dbcfe9ef08c20b9854d8454510ba429cdb1f56c318d99f62d6e19a41ca2e8
|
7
|
+
data.tar.gz: 33a427ee981915f75cfdbf3f4143ddc2d6349db562f03fb59884f5f7562660c3f59d5f2e781b03addd7f33c1768750aa42fcf6430b39543a8594f02d4e1f2cfe
|
data/README.md
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
# NMEA Ruby Gem (nmea_plus)
|
2
|
+
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/nmea_plus.svg)](https://rubygems.org/gems/nmea_plus)
|
4
|
+
[![Build Status](https://travis-ci.org/ifreecarve/nmea_plus.svg)](https://travis-ci.org/ifreecarve/nmea_plus)
|
5
|
+
|
6
|
+
[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.
|
7
|
+
|
8
|
+
|
9
|
+
## Install
|
10
|
+
|
11
|
+
`gem install nmea_plus`
|
12
|
+
|
13
|
+
## Example
|
14
|
+
|
15
|
+
In its most basic use, you can decode messages as follows:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
require 'nmea_plus'
|
19
|
+
|
20
|
+
decoder = NMEAPlus::Decoder.new
|
21
|
+
message = decoder.parse("$GPGLL,4916.45,N,12311.12,W,225444,A*00")
|
22
|
+
# message data -- specific to this message type
|
23
|
+
puts message.latitude # prints 49.27416666666666666666
|
24
|
+
puts message.longitude # prints -123.18533333333333333
|
25
|
+
puts message.fix_time # prints <today's date> 22:54:44 <your local time zone offset>
|
26
|
+
puts message.valid? # prints true
|
27
|
+
puts message.faa_mode # prints nil
|
28
|
+
|
29
|
+
# metadata
|
30
|
+
puts message.checksum_ok? # prints false -- because this checksum is made up
|
31
|
+
puts message.original # prints "$GPGLL,4916.45,N,12311.12,W,225444,A*00"
|
32
|
+
puts message.data_type # prints "GPGLL" -- what was specified in the message
|
33
|
+
puts message.interpreted_data_type # prints "GLL" -- the actual container used
|
34
|
+
|
35
|
+
# metadata that applies to multipart messages (also works for single messages)
|
36
|
+
puts message.all_messages_received? # prints true
|
37
|
+
puts message.all_checksums_ok? # prints false -- checksum is still made up
|
38
|
+
|
39
|
+
# safer way to do what we did above
|
40
|
+
if "GPGLL" == message.data_type # Alternately, if "GLL" == message.interpreted_data_type
|
41
|
+
puts message.latitude # prints 49.27416666666666666666
|
42
|
+
puts message.longitude # prints -123.18533333333333333
|
43
|
+
puts message.fix_time # prints <today's date> 22:54:44 <your local time zone offset>
|
44
|
+
puts message.valid? # prints true
|
45
|
+
puts message.faa_mode # prints nil
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
|
50
|
+
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 object 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`.
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
require 'nmea_plus'
|
54
|
+
|
55
|
+
input1 = "!AIVDM,2,1,0,A,58wt8Ui`g??r21`7S=:22058<v05Htp000000015>8OA;0sk,0*7B"
|
56
|
+
input2 = "!AIVDM,2,2,0,A,eQ8823mDm3kP00000000000,2*5D"
|
57
|
+
io_source = StringIO.new("#{input1}\n#{input2}") # source decoder works on any IO object
|
58
|
+
|
59
|
+
source_decoder = NMEAPlus::SourceDecoder.new(io_source)
|
60
|
+
source_decoder.each_complete_message do |message|
|
61
|
+
puts message_all_checksums_ok? # prints true -- the full message set has good checksums
|
62
|
+
puts message_all_messages_received? # prints true -- taken care of by each_complete_message
|
63
|
+
if "AIVDM" == message.data_type
|
64
|
+
puts message.ais.message_type # prints 5
|
65
|
+
puts message.ais.repeat_indicator # prints 0
|
66
|
+
puts message.ais.source_mmsi # prints 603916439
|
67
|
+
puts message.ais.ais_version # prints 0
|
68
|
+
puts message.ais.imo_number # prints 439303422
|
69
|
+
puts message.ais.callsign.strip # prints "ZA83R"
|
70
|
+
puts message.ais.name.strip # prints "ARCO AVON"
|
71
|
+
puts message.ais.ship_cargo_type # prints 69
|
72
|
+
puts message.ais.ship_dimension_to_bow # prints 113
|
73
|
+
puts message.ais.ship_dimension_to_stern # prints 31
|
74
|
+
puts message.ais.ship_dimension_to_port # prints 17
|
75
|
+
puts message.ais.ship_dimension_to_starboard # prints 11
|
76
|
+
puts message.ais.epfd_type # prints 0
|
77
|
+
puts message.ais.eta # prints <this year>-03-23 19:45:00 <your local time zone offset>
|
78
|
+
puts message.ais.static_draught # prints 13.2
|
79
|
+
puts message.ais.destination.strip # prints "HOUSTON"
|
80
|
+
puts message.ais.dte? # prints false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
```
|
85
|
+
|
86
|
+
## Design documents
|
87
|
+
|
88
|
+
This gem was coded to accept the standard NMEA messages defined in the unoffical spec found here:
|
89
|
+
http://www.catb.org/gpsd/NMEA.txt
|
90
|
+
|
91
|
+
Because the message types are standard, if no override is found for a particular talker ID then the message will parse according to the command (the last 3 characters) of the data type. In other words, $GPGLL will use the general GLL message type. Currently, the following standard message types are supported:
|
92
|
+
|
93
|
+
> 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
|
94
|
+
|
95
|
+
|
96
|
+
Additionally, AIS message type definitions were implemented from the unofficial spec found here:
|
97
|
+
http://catb.org/gpsd/AIVDM.html
|
98
|
+
|
99
|
+
> Currently AIVDM messages types 1, 2, 3, 5, and 8 are implemented.
|
100
|
+
|
101
|
+
Support for proprietary messages is also possible. PASHR is included as proof-of-concept.
|
102
|
+
|
103
|
+
|
104
|
+
## Disclaimer
|
105
|
+
|
106
|
+
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 your ship to this code without doing your own rigorous testing.
|
107
|
+
|
108
|
+
|
109
|
+
## Author
|
110
|
+
|
111
|
+
This gem was written by Ian Katz (ifreecarve@gmail.com) in 2015. It's released under the Apache 2.0 license.
|
112
|
+
|
113
|
+
|
114
|
+
## See Also
|
115
|
+
|
116
|
+
* [Contributing](CONTRIBUTING.md)
|
@@ -3,11 +3,22 @@ require 'nmea_plus/version'
|
|
3
3
|
require 'nmea_plus/generated_parser/parser'
|
4
4
|
require 'nmea_plus/generated_parser/tokenizer'
|
5
5
|
|
6
|
+
# NMEAPlus contains classes for parsing and decoding NMEA and AIS messages.
|
7
|
+
# @author Ian Katz
|
6
8
|
module NMEAPlus
|
9
|
+
|
10
|
+
# The NMEA source decoder wraps an IO object, converting each_line functionality
|
11
|
+
# to {#each_message} or {#each_complete_message}
|
7
12
|
class SourceDecoder
|
13
|
+
# False by default.
|
14
|
+
# @return [bool] whether to throw an exception on lines that don't properly parse
|
8
15
|
attr_accessor :throw_on_parse_fail
|
9
|
-
attr_accessor :throw_on_unrecognized_type # typically for development
|
10
16
|
|
17
|
+
# False by default. Typically for development.
|
18
|
+
# @return [bool] whether to throw an exception on message types that aren't supported
|
19
|
+
attr_accessor :throw_on_unrecognized_type
|
20
|
+
|
21
|
+
# @param line_reader [IO] The source stream for messages
|
11
22
|
def initialize(line_reader)
|
12
23
|
unless line_reader.respond_to? :each_line
|
13
24
|
fail ArgumentError, "line_reader must inherit from type IO (or implement each_line)"
|
@@ -17,7 +28,9 @@ module NMEAPlus
|
|
17
28
|
@decoder = NMEAPlus::Decoder.new
|
18
29
|
end
|
19
30
|
|
20
|
-
#
|
31
|
+
# Executes the block for every valid NMEA message in the source stream
|
32
|
+
# @yield [NMEAPlus::Message] A parsed message
|
33
|
+
# @return [void]
|
21
34
|
def each_message
|
22
35
|
@source.each_line do |line|
|
23
36
|
if @throw_on_parse_fail
|
@@ -34,7 +47,10 @@ module NMEAPlus
|
|
34
47
|
end
|
35
48
|
end
|
36
49
|
|
37
|
-
#
|
50
|
+
# Executes the block for every valid NMEA message in the source stream, attempting to
|
51
|
+
# group multipart messages into message chains.
|
52
|
+
# @yield [NMEAPlus::Message] A parsed message that may contain subsequent parts
|
53
|
+
# @return [void]
|
38
54
|
def each_complete_message
|
39
55
|
partials = {}
|
40
56
|
each_message do |msg|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
require_relative 'message_factory'
|
3
|
+
|
4
|
+
require_relative 'message/ais/vdm'
|
5
|
+
|
6
|
+
module NMEAPlus
|
7
|
+
|
8
|
+
# Defines a factory for AIS messages, which will all use {NMEAPlus::Message::AIS::AISMessage} as their base
|
9
|
+
class AISMessageFactory < MessageFactory
|
10
|
+
# @return [String] The name of the parent module: AIS
|
11
|
+
def self.parent_module
|
12
|
+
"AIS"
|
13
|
+
end
|
14
|
+
|
15
|
+
# Match all AIS messages as their generic counterparts. AIVDM becomes VDM, etc.
|
16
|
+
# @param data_type [String] The data_type of the AIS message (e.g. the AIVDM of "$AIVDM,12,3,,4,5*00")
|
17
|
+
# @return [String] The data_type that we will attempt to use in decoding the message (e.g. VDM)
|
18
|
+
def self.alternate_data_type(data_type)
|
19
|
+
# match last 3 digits (get rid of talker)
|
20
|
+
data_type[2..4]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
File without changes
|
File without changes
|
@@ -1,9 +1,5 @@
|
|
1
1
|
require_relative "base_ais"
|
2
2
|
|
3
|
-
require_relative "vdm_payload/vdm_msg1"
|
4
|
-
require_relative "vdm_payload/vdm_msg5"
|
5
|
-
require_relative "vdm_payload/vdm_msg8"
|
6
|
-
|
7
3
|
=begin boilerplate for vdm payload objects
|
8
4
|
require_relative 'vdm_msg'
|
9
5
|
|
@@ -21,6 +17,11 @@ end
|
|
21
17
|
|
22
18
|
=end
|
23
19
|
|
20
|
+
require_relative "vdm_payload/vdm_msg1"
|
21
|
+
require_relative "vdm_payload/vdm_msg5"
|
22
|
+
require_relative "vdm_payload/vdm_msg8"
|
23
|
+
|
24
|
+
|
24
25
|
module NMEAPlus
|
25
26
|
module Message
|
26
27
|
module AIS
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,235 @@
|
|
1
|
+
|
2
|
+
module NMEAPlus
|
3
|
+
|
4
|
+
# The module containing all parsed message types.
|
5
|
+
# @see NMEAPlus::Message::Base
|
6
|
+
module Message
|
7
|
+
|
8
|
+
# The base message type, from which all others inherit
|
9
|
+
# @abstract
|
10
|
+
class Base
|
11
|
+
# make our own shortcut syntax for message attribute accessors
|
12
|
+
# @param name [String] What the accessor will be called
|
13
|
+
# @param field_num [Integer] The index of the field in the payload
|
14
|
+
# @param formatter [Symbol] The symbol for the formatting function to apply to the field (optional)
|
15
|
+
# @!macro [attach] field_reader
|
16
|
+
# @method $1
|
17
|
+
# @return field $2 of the payload, formatted with the function {#$3}
|
18
|
+
def self.field_reader(name, field_num, formatter = nil)
|
19
|
+
if formatter.nil?
|
20
|
+
self.class_eval("def #{name};@fields[#{field_num}];end")
|
21
|
+
else
|
22
|
+
self.class_eval("def #{name};#{formatter}(@fields[#{field_num}]);end")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [String] The single character prefix for this NMEA 0183 message type
|
27
|
+
attr_accessor :prefix
|
28
|
+
|
29
|
+
# @return [String] The unprocessed payload of the message
|
30
|
+
attr_reader :payload
|
31
|
+
|
32
|
+
# @return [Array<String>] The payload of the message, split into fields
|
33
|
+
attr_reader :fields
|
34
|
+
|
35
|
+
# @return [String] The two-character checksum of the message
|
36
|
+
attr_accessor :checksum
|
37
|
+
|
38
|
+
# @return [String] The data type used by the {MessageFactory} to parse this message
|
39
|
+
attr_accessor :interpreted_data_type
|
40
|
+
|
41
|
+
# @return [NMEAPlus::Message] The next part of a multipart message, if available
|
42
|
+
attr_accessor :next_part
|
43
|
+
|
44
|
+
field_reader :data_type, 0, nil
|
45
|
+
|
46
|
+
# @!parse attr_reader :original
|
47
|
+
# @return [String] The original message
|
48
|
+
def original
|
49
|
+
"#{prefix}#{payload}*#{checksum}"
|
50
|
+
end
|
51
|
+
|
52
|
+
# @!parse attr_accessor :payload
|
53
|
+
def payload=(val)
|
54
|
+
@payload = val
|
55
|
+
@fields = val.split(',', -1)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [bool] Whether the checksum calculated from the payload matches the checksum given in the message
|
59
|
+
def checksum_ok?
|
60
|
+
calculated_checksum.upcase == checksum.upcase
|
61
|
+
end
|
62
|
+
|
63
|
+
# return [bool] Whether the checksums for all available message parts are OK
|
64
|
+
def all_checksums_ok?
|
65
|
+
return false unless checksum_ok?
|
66
|
+
return true if @next_part.nil?
|
67
|
+
@next_part.all_checksums_ok?
|
68
|
+
end
|
69
|
+
|
70
|
+
# return [String] The calculated checksum for this payload as a two-character string
|
71
|
+
def calculated_checksum
|
72
|
+
"%02x" % @payload.each_byte.map(&:ord).reduce(:^)
|
73
|
+
end
|
74
|
+
|
75
|
+
# @!parse attr_reader :total_messages
|
76
|
+
# @abstract
|
77
|
+
# @see #message_number
|
78
|
+
# @return [Integer] The number of parts to this message
|
79
|
+
def total_messages
|
80
|
+
1
|
81
|
+
end
|
82
|
+
|
83
|
+
# @!parse attr_reader :message_number
|
84
|
+
# @abstract
|
85
|
+
# @see #total_messages
|
86
|
+
# @return [Integer] The ordinal number of this message in its sequence
|
87
|
+
def message_number
|
88
|
+
1
|
89
|
+
end
|
90
|
+
|
91
|
+
# Create a linked list of messages by appending a new message to the end of the chain that starts
|
92
|
+
# with this message. (O(n) implementation; message parts assumed to be < 10)
|
93
|
+
# @param msg [NMEAPlus::Message] The latest message in the chain
|
94
|
+
# @return [void]
|
95
|
+
def add_message_part(msg)
|
96
|
+
if @next_part.nil?
|
97
|
+
@next_part = msg
|
98
|
+
else
|
99
|
+
@next_part.add_message_part(msg)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# @return [bool] Whether all messages in a multipart message have been received.
|
104
|
+
def all_messages_received?
|
105
|
+
total_messages == highest_contiguous_index
|
106
|
+
end
|
107
|
+
|
108
|
+
# @return [Integer] The highest contiguous sequence number of linked message parts
|
109
|
+
# @see #message_number
|
110
|
+
# @see #_highest_contiguous_index
|
111
|
+
def highest_contiguous_index
|
112
|
+
_highest_contiguous_index(0)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Helper function to calculate the contiguous index
|
116
|
+
# @param last_index [Integer] the index of the starting message
|
117
|
+
# @see #highest_contiguous_index
|
118
|
+
# @return [Integer] The highest contiguous sequence number of linked message parts
|
119
|
+
def _highest_contiguous_index(last_index)
|
120
|
+
mn = message_number # just in case this is expensive to compute
|
121
|
+
return last_index if mn - last_index != 1 # indicating a skip or restart
|
122
|
+
return mn if @next_part.nil? # indicating we're the last message
|
123
|
+
@next_part._highest_contiguous_index(mn) # recurse down
|
124
|
+
end
|
125
|
+
|
126
|
+
######################### Conversion functions
|
127
|
+
|
128
|
+
# Convert A string latitude or longitude as fields into a signed number
|
129
|
+
# @param dm_string [String] An angular measurement in the form DDMM.MMM
|
130
|
+
# @param sign_letter [String] can be N,S,E,W
|
131
|
+
# @return [Float] A signed latitude or longitude
|
132
|
+
def _degrees_minutes_to_decimal(dm_string, sign_letter = "")
|
133
|
+
return nil if dm_string.nil? || dm_string.empty?
|
134
|
+
r = /(\d+)(\d{2}\.\d+)/ # (some number of digits) (2 digits for minutes).(decimal minutes)
|
135
|
+
m = r.match(dm_string)
|
136
|
+
raw = m.values_at(1)[0].to_f + (m.values_at(2)[0].to_f / 60)
|
137
|
+
_nsew_signed_float(raw, sign_letter)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Use cardinal directions to assign positive or negative to mixed_val.
|
141
|
+
# Of possible directions NSEW (sign_letter) treat N/E as + and S/W as -
|
142
|
+
# @param mixed_val [String] input value, can be string or float
|
143
|
+
# @param sign_letter [String] can be N,S,E,W, or empty
|
144
|
+
# @return [Float] The input value signed as per the sign letter.
|
145
|
+
def _nsew_signed_float(mixed_val, sign_letter = "")
|
146
|
+
value = mixed_val.to_f
|
147
|
+
value *= -1 if !sign_letter.empty? && "SW".include?(sign_letter.upcase)
|
148
|
+
value
|
149
|
+
end
|
150
|
+
|
151
|
+
# integer or nil
|
152
|
+
# @param field [String] the field index to check
|
153
|
+
# @return [Integer] The value in the field or nil
|
154
|
+
def _integer(field)
|
155
|
+
return nil if field.nil? || field.empty?
|
156
|
+
field.to_i
|
157
|
+
end
|
158
|
+
|
159
|
+
# float or nil
|
160
|
+
# @param field [String] the field index to check
|
161
|
+
# @return [Float] The value in the field or nil
|
162
|
+
def _float(field)
|
163
|
+
return nil if field.nil? || field.empty?
|
164
|
+
field.to_f
|
165
|
+
end
|
166
|
+
|
167
|
+
# string or nil
|
168
|
+
# @param field [String] the field index to check
|
169
|
+
# @return [String] The value in the field or nil
|
170
|
+
def _string(field)
|
171
|
+
return nil if field.nil? || field.empty?
|
172
|
+
field
|
173
|
+
end
|
174
|
+
|
175
|
+
# hex to int or nil
|
176
|
+
# @param field [String] the field index to check
|
177
|
+
# @return [Integer] The value in the field or nil
|
178
|
+
def _hex_to_integer(field)
|
179
|
+
return nil if field.nil? || field.empty?
|
180
|
+
field.hex
|
181
|
+
end
|
182
|
+
|
183
|
+
# utc time or nil (HHMMSS or HHMMSS.SS)
|
184
|
+
# @param field [String] the field index to check
|
185
|
+
# @return [Time] The value in the field or nil
|
186
|
+
def _utctime_hms(field)
|
187
|
+
return nil if field.nil? || field.empty?
|
188
|
+
re_format = /(\d{2})(\d{2})(\d{2}(\.\d+)?)/
|
189
|
+
now = Time.now
|
190
|
+
begin
|
191
|
+
hms = re_format.match(field)
|
192
|
+
Time.new(now.year, now.month, now.day, hms[1].to_i, hms[2].to_i, hms[3].to_f)
|
193
|
+
rescue
|
194
|
+
nil
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# time interval or nil (HHMMSS or HHMMSS.SS)
|
199
|
+
# @param field [String] the field index to check
|
200
|
+
# @return [Time] The value in the field or nil
|
201
|
+
def _interval_hms(field)
|
202
|
+
return nil if field.nil? || field.empty?
|
203
|
+
re_format = /(\d{2})(\d{2})(\d{2}(\.\d+)?)/
|
204
|
+
begin
|
205
|
+
hms = re_format.match(field)
|
206
|
+
Time.new(0, 0, 0, hms[1].to_i, hms[2].to_i, hms[3].to_f)
|
207
|
+
rescue
|
208
|
+
nil
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# @param d_field [String] the date field index to check
|
213
|
+
# @param t_field [String] the time field index to check
|
214
|
+
# @return [Time] The value in the fields, or nil if either is not provided
|
215
|
+
def _utc_date_time(d_field, t_field)
|
216
|
+
return nil if t_field.nil? || t_field.empty?
|
217
|
+
return nil if d_field.nil? || d_field.empty?
|
218
|
+
|
219
|
+
# get formats and time
|
220
|
+
time_format = /(\d{2})(\d{2})(\d{2}(\.\d+)?)/
|
221
|
+
date_format = /(\d{2})(\d{2})(\d{2})/
|
222
|
+
|
223
|
+
# crunch numbers
|
224
|
+
begin
|
225
|
+
dmy = date_format.match(d_field)
|
226
|
+
hms = time_format.match(t_field)
|
227
|
+
Time.new(2000 + dmy[3].to_i, dmy[2].to_i, dmy[1].to_i, hms[1].to_i, hms[2].to_i, hms[3].to_f)
|
228
|
+
rescue
|
229
|
+
nil
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|