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.
gem install nmea_plus
In its most basic use, you can decode messages as follows:
require 'nmea_plus'
decoder = NMEAPlus::Decoder.new
message = decoder.parse("$GPGLL,4916.45,N,12311.12,W,225444,A*00")
# message data -- specific to this message type
puts message.latitude # prints 49.27416666666666666666
puts message.longitude # prints -123.18533333333333333
puts message.fix_time # prints <today's date> 22:54:44 <your local time zone offset>
puts message.valid? # prints true
puts message.faa_mode # prints nil
# metadata
puts message.checksum_ok? # prints false -- because this checksum is made up
puts message.original # prints "$GPGLL,4916.45,N,12311.12,W,225444,A*00"
puts message.data_type # prints "GPGLL" -- what was specified in the message
puts message.interpreted_data_type # prints "GLL" -- the actual container used
# metadata that applies to multipart messages (also works for single messages)
puts message.all_messages_received? # prints true
puts message.all_checksums_ok? # prints false -- checksum is still made up
# safer way to do what we did above
if "GPGLL" == message.data_type # Alternately, if "GLL" == message.interpreted_data_type
puts message.latitude # prints 49.27416666666666666666
puts message.longitude # prints -123.18533333333333333
puts message.fix_time # prints <today's date> 22:54:44 <your local time zone offset>
puts message.valid? # prints true
puts message.faa_mode # prints nil
end
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
.
require 'nmea_plus'
input1 = "!AIVDM,2,1,0,A,58wt8Ui`g??r21`7S=:22058<v05Htp000000015>8OA;0sk,0*7B"
input2 = "!AIVDM,2,2,0,A,eQ8823mDm3kP00000000000,2*5D"
io_source = StringIO.new("#{input1}\n#{input2}") # source decoder works on any IO object
source_decoder = NMEAPlus::SourceDecoder.new(io_source)
source_decoder.each_complete_message do |message|
puts message_all_checksums_ok? # prints true -- the full message set has good checksums
puts message_all_messages_received? # prints true -- taken care of by each_complete_message
if "AIVDM" == message.data_type
puts message.ais.message_type # prints 5
puts message.ais.repeat_indicator # prints 0
puts message.ais.source_mmsi # prints 603916439
puts message.ais.ais_version # prints 0
puts message.ais.imo_number # prints 439303422
puts message.ais.callsign.strip # prints "ZA83R"
puts message.ais.name.strip # prints "ARCO AVON"
puts message.ais.ship_cargo_type # prints 69
puts message.ais.ship_dimension_to_bow # prints 113
puts message.ais.ship_dimension_to_stern # prints 31
puts message.ais.ship_dimension_to_port # prints 17
puts message.ais.ship_dimension_to_starboard # prints 11
puts message.ais.epfd_type # prints 0
puts message.ais.eta # prints <this year>-03-23 19:45:00 <your local time zone offset>
puts message.ais.static_draught # prints 13.2
puts message.ais.destination.strip # prints "HOUSTON"
puts message.ais.dte? # prints false
end
end
This gem was coded to accept the standard NMEA messages defined in the unoffical spec found here: http://www.catb.org/gpsd/NMEA.txt
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:
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
Support for proprietary NMEA messages is also possible. PASHR is included as proof-of-concept.
AIS message type definitions were implemented from the unofficial spec found here: http://catb.org/gpsd/AIVDM.html
And some binary subtypes from the ITU spec found here: https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.1371-4-201004-S!!PDF-E.pdf
The AIS payload can be found in the payload field of a VDM
message (aka !AIVDM
, !ABVDM
, !SAVDM
). Currently, the following AIS message types are supported:
1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 18, 19, 20, 21, 24, 27
Type 6 subtypes for DAC/FID: 1/0, 1/2, 1/3, 1/4, 1/5, 235/10, 1022/61
Type 8 subtypes for DAC/FID: 1/0, 1/22, 1/31, 366/56, 366/57
This module was written primarily from information scraped together on the web, with minimal testing on an actual device (an SR161 AIS receiver). 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.
This gem was written by Ian Katz ([email protected]) in 2015. It's released under the Apache 2.0 license.