class NMEAPlus::Message::Base
The base NMEA
message type, from which all others inherit. Messages have a prefix character, fields, and checksum. This class provides convenience functions for accessing the fields as the appropriate data type, and logic for constructing multipart messages. @abstract
Attributes
@return [String] The two-character checksum of the message
@return [Array<String>] The payload of the message, split into fields
@return [String] The data type used by the {MessageFactory} to parse this message
@return [NMEAPlus::Message] The next part of a multipart message, if available
@return [String] The unprocessed payload of the message
@return [String] The single character prefix for this NMEA
0183 message type
Public Class Methods
Source
# File lib/nmea_plus/message/base.rb, line 181 def self._float(field) return nil if field.nil? || field.empty? field.to_f end
float or nil. This function is meant to be passed as a formatter to {field_reader}. @param field [String] the value in the field to be checked @return [Float] The value in the field or nil
Source
# File lib/nmea_plus/message/base.rb, line 201 def self._hex_to_integer(field) return nil if field.nil? || field.empty? field.hex end
hex to int or nil. This function is meant to be passed as a formatter to {field_reader}. @param field [String] the value in the field to be checked @return [Integer] The value in the field or nil
Source
# File lib/nmea_plus/message/base.rb, line 171 def self._integer(field) return nil if field.nil? || field.empty? field.to_i end
integer or nil. This function is meant to be passed as a formatter to {field_reader}. @param field [String] the value in the field to be checked @return [Integer] The value in the field or nil
Source
# File lib/nmea_plus/message/base.rb, line 228 def self._interval_hms(field) return nil if field.nil? || field.empty? re_format = /(\d{2})(\d{2})(\d{2}(\.\d+)?)/ begin hms = re_format.match(field) Time.new(0, 1, 1, hms[1].to_i, hms[2].to_i, hms[3].to_f, "+00:00") rescue nil end end
time interval or nil (HHMMSS or HHMMSS.SS). This function is meant to be passed as a formatter to {field_reader}. @param field [String] the value in the field to be checked @return [Time] The value in the field or nil
Source
# File lib/nmea_plus/message/base.rb, line 191 def self._string(field) return nil if field.nil? || field.empty? field end
string or nil. This function is meant to be passed as a formatter to {field_reader}. @param field [String] the value in the field to be checked @return [String] The value in the field or nil
Source
# File lib/nmea_plus/message/base.rb, line 244 def self._utc_date_time(d_field, t_field) return nil if t_field.nil? || t_field.empty? return nil if d_field.nil? || d_field.empty? # get formats and time time_format = /(\d{2})(\d{2})(\d{2}(\.\d+)?)/ date_format = /(\d{2})(\d{2})(\d{2})/ # crunch numbers begin dmy = date_format.match(d_field) hms = time_format.match(t_field) 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, "+00:00") rescue nil end end
Create a Time object from a date and time field @param d_field [String] the date value in the field to be checked @param t_field [String] the time value in the field to be checked @return [Time] The value in the fields, or nil if either is not provided
Source
# File lib/nmea_plus/message/base.rb, line 211 def self._utctime_hms(field) return nil if field.nil? || field.empty? re_format = /(\d{2})(\d{2})(\d{2}(\.\d+)?)/ now = Time.now begin hms = re_format.match(field) Time.new(now.year, now.month, now.day, hms[1].to_i, hms[2].to_i, hms[3].to_f, "+00:00") rescue nil end end
utc time or nil (HHMMSS or HHMMSS.SS). This function is meant to be passed as a formatter to {field_reader}. @param field [String] the value in the field to be checked @return [Time] The value in the field or nil
Source
# File lib/nmea_plus/message/base.rb, line 147 def self.degrees_minutes_to_decimal(dm_string, sign_letter = "") return nil if dm_string.nil? || dm_string.empty? r = /(\d+)(\d{2}\.\d+)/ # (some number of digits) (2 digits for minutes).(decimal minutes) m = r.match(dm_string) raw = m.values_at(1)[0].to_f + (m.values_at(2)[0].to_f / 60) nsew_signed_float(raw, sign_letter) end
Convert A string latitude or longitude as fields into a signed number @param dm_string [String] An angular measurement in the form DDMM.MMM @param sign_letter [String] can be N,S,E,W @return [Float] A signed latitude or longitude
Source
# File lib/nmea_plus/message/base.rb, line 30 def self.field_reader(name, field_num, formatter = nil) if formatter.nil? define_method(name) { @fields[field_num] } else define_method(name) { self.class.send(formatter.to_sym, @fields[field_num]) } end end
Enable a shortcut syntax for message attribute accessors, in the style of ‘attr_accessor` metaprogramming. This is used to create a named field pointing to a specific indexed field in the payload, optionally applying a specific formatting function.
The formatting function MUST be a static method on this class. This is a limitation caused by the desire to both (1) expose the formatters outside this class, and (2) use them for metaprogramming without the having to name the entire function. field_reader is a static method, so if not for the fact that `self.class.methods.include? formatter` fails to work for class methods in this context (unlike `self.methods.include?`, which properly finds instance methods), I would allow either one and just conditionally `define_method` the proper definition
@param name [String] What the accessor will be called @param field_num [Integer] The index of the field in the payload @param formatter [Symbol] The symbol for the formatting function to apply to the field (optional) @return [void] @macro [attach] field_reader
@!attribute [r] $1 @return field $2 of the payload, formatted with the function {$3}
Source
# File lib/nmea_plus/message/base.rb, line 161 def self.nsew_signed_float(mixed_val, sign_letter = "") value = mixed_val.to_f value *= -1 if !sign_letter.empty? && "SW".include?(sign_letter.upcase) value end
Use cardinal directions to assign positive or negative to mixed_val Of possible directions NSEW (sign_letter) treat N/E as + and S/W as - @param mixed_val [String] input value, can be string or float @param sign_letter [String] can be N,S,E,W, or empty @return [Float] The input value signed as per the sign letter.
Public Instance Methods
Source
# File lib/nmea_plus/message/base.rb, line 133 def _highest_contiguous_index(last_index) mn = message_number # just in case this is expensive to compute return last_index if mn - last_index != 1 # indicating a skip or restart return mn if @next_part.nil? # indicating we're the last message @next_part._highest_contiguous_index(mn) # recurse down end
Helper function to calculate the contiguous index @param last_index [Integer] the index of the starting message @see highest_contiguous_index
@return [Integer] The highest contiguous sequence number of linked message parts
Source
# File lib/nmea_plus/message/base.rb, line 109 def add_message_part(msg) if @next_part.nil? @next_part = msg else @next_part.add_message_part(msg) end end
Create a linked list of messages by appending a new message to the end of the chain that starts with this message. (O(n) implementation; message parts assumed to be < 10) @param msg [NMEAPlus::Message] The latest message in the chain @return [void]
Source
# File lib/nmea_plus/message/base.rb, line 77 def all_checksums_ok? return false unless checksum_ok? return true if @next_part.nil? @next_part.all_checksums_ok? end
return [bool] Whether the checksums for all available message parts are OK
Source
# File lib/nmea_plus/message/base.rb, line 118 def all_messages_received? total_messages == highest_contiguous_index end
@return [bool] Whether all messages in a multipart message have been received.
Source
# File lib/nmea_plus/message/base.rb, line 85 def calculated_checksum format("%02x", @payload.each_byte.map(&:ord).reduce(:^)) end
return [String] The calculated checksum for this payload as a two-character string
Source
# File lib/nmea_plus/message/base.rb, line 72 def checksum_ok? calculated_checksum.casecmp(checksum).zero? end
@return [bool] Whether the checksum calculated from the payload matches the checksum given in the message
Source
# File lib/nmea_plus/message/base.rb, line 125 def highest_contiguous_index _highest_contiguous_index(0) end
@return [Integer] The highest contiguous sequence number of linked message parts @see message_number
@see _highest_contiguous_index
Source
# File lib/nmea_plus/message/base.rb, line 101 def message_number 1 end
@!parse attr_reader :message_number @abstract @see total_messages
@return [Integer] The ordinal number of this message in its sequence
Source
# File lib/nmea_plus/message/base.rb, line 61 def original "#{prefix}#{payload}*#{checksum}" end
@!parse attr_reader :original @return [String] The original message
Source
# File lib/nmea_plus/message/base.rb, line 66 def payload=(val) @payload = val @fields = val.split(",", -1) end
@!parse attr_accessor :payload
Source
# File lib/nmea_plus/message/base.rb, line 93 def total_messages 1 end
@!parse attr_reader :total_messages @abstract @see message_number
@return [Integer] The number of parts to this message