Class: NMEAPlus::Message::Base Abstract

Inherits:
Object
  • Object
show all
Defined in:
lib/nmea_plus/message/base.rb

Overview

This class is abstract.

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.

Direct Known Subclasses

AIS::AISMessage, NMEA::NMEAMessage

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#checksumString

Returns The two-character checksum of the message.

Returns:

  • (String)

    The two-character checksum of the message



41
42
43
# File 'lib/nmea_plus/message/base.rb', line 41

def checksum
  @checksum
end

#data_typeString (readonly)

Returns:

  • (String)

    The NMEA data type of this message

  • field 0 of the payload, formatted with the function #nil



50
# File 'lib/nmea_plus/message/base.rb', line 50

field_reader :data_type, 0, nil

#fieldsArray<String> (readonly)

Returns The payload of the message, split into fields.

Returns:

  • (Array<String>)

    The payload of the message, split into fields



38
39
40
# File 'lib/nmea_plus/message/base.rb', line 38

def fields
  @fields
end

#interpreted_data_typeString

Returns The data type used by the NMEAPlus::MessageFactory to parse this message.

Returns:



44
45
46
# File 'lib/nmea_plus/message/base.rb', line 44

def interpreted_data_type
  @interpreted_data_type
end

#message_numberInteger (readonly)

This method is abstract.

Returns The ordinal number of this message in its sequence.

Returns:

  • (Integer)

    The ordinal number of this message in its sequence

See Also:



93
94
95
# File 'lib/nmea_plus/message/base.rb', line 93

def message_number
  1
end

#next_partNMEAPlus::Message

Returns The next part of a multipart message, if available.

Returns:



47
48
49
# File 'lib/nmea_plus/message/base.rb', line 47

def next_part
  @next_part
end

#originalString (readonly)

Returns The original message.

Returns:

  • (String)

    The original message



54
55
56
# File 'lib/nmea_plus/message/base.rb', line 54

def original
  "#{prefix}#{payload}*#{checksum}"
end

#payloadString

Returns The unprocessed payload of the message.

Returns:

  • (String)

    The unprocessed payload of the message



35
36
37
# File 'lib/nmea_plus/message/base.rb', line 35

def payload
  @payload
end

#prefixString

Returns The single character prefix for this NMEA 0183 message type.

Returns:

  • (String)

    The single character prefix for this NMEA 0183 message type



32
33
34
# File 'lib/nmea_plus/message/base.rb', line 32

def prefix
  @prefix
end

#total_messagesInteger (readonly)

This method is abstract.

Returns The number of parts to this message.

Returns:

  • (Integer)

    The number of parts to this message

See Also:



85
86
87
# File 'lib/nmea_plus/message/base.rb', line 85

def total_messages
  1
end

Class Method Details

.degrees_minutes_to_decimal(dm_string, sign_letter = "") ⇒ Float

Convert A string latitude or longitude as fields into a signed number

Parameters:

  • dm_string (String)

    An angular measurement in the form DDMM.MMM

  • sign_letter (String) (defaults to: "")

    can be N,S,E,W

Returns:

  • (Float)

    A signed latitude or longitude



138
139
140
141
142
143
144
# File 'lib/nmea_plus/message/base.rb', line 138

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

.field_reader(name, field_num, formatter = nil) ⇒ void

This method returns an undefined value.

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.

Parameters:

  • name (String)

    What the accessor will be called

  • field_num (Integer)

    The index of the field in the payload

  • formatter (Symbol) (defaults to: nil)

    The symbol for the formatting function to apply to the field (optional)



23
24
25
26
27
28
29
# File 'lib/nmea_plus/message/base.rb', line 23

def self.field_reader(name, field_num, formatter = nil)
  if formatter.nil?
    self.class_eval("def #{name};@fields[#{field_num}];end")
  else
    self.class_eval("def #{name};#{formatter}(@fields[#{field_num}]);end")
  end
end

.nsew_signed_float(mixed_val, sign_letter = "") ⇒ Float

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 -

Parameters:

  • mixed_val (String)

    input value, can be string or float

  • sign_letter (String) (defaults to: "")

    can be N,S,E,W, or empty

Returns:

  • (Float)

    The input value signed as per the sign letter.



151
152
153
154
155
# File 'lib/nmea_plus/message/base.rb', line 151

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

Instance Method Details

#_float(field) ⇒ Float

float or nil. This function is meant to be passed as a formatter to field_reader.

Parameters:

  • field (String)

    the value in the field to be checked

Returns:

  • (Float)

    The value in the field or nil



170
171
172
173
# File 'lib/nmea_plus/message/base.rb', line 170

def _float(field)
  return nil if field.nil? || field.empty?
  field.to_f
end

#_hex_to_integer(field) ⇒ Integer

hex to int or nil. This function is meant to be passed as a formatter to field_reader.

Parameters:

  • field (String)

    the value in the field to be checked

Returns:

  • (Integer)

    The value in the field or nil



188
189
190
191
# File 'lib/nmea_plus/message/base.rb', line 188

def _hex_to_integer(field)
  return nil if field.nil? || field.empty?
  field.hex
end

#_highest_contiguous_index(last_index) ⇒ Integer

Helper function to calculate the contiguous index

Parameters:

  • last_index (Integer)

    the index of the starting message

Returns:

  • (Integer)

    The highest contiguous sequence number of linked message parts

See Also:



125
126
127
128
129
130
# File 'lib/nmea_plus/message/base.rb', line 125

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

#_integer(field) ⇒ Integer

integer or nil. This function is meant to be passed as a formatter to field_reader.

Parameters:

  • field (String)

    the value in the field to be checked

Returns:

  • (Integer)

    The value in the field or nil



161
162
163
164
# File 'lib/nmea_plus/message/base.rb', line 161

def _integer(field)
  return nil if field.nil? || field.empty?
  field.to_i
end

#_interval_hms(field) ⇒ Time

time interval or nil (HHMMSS or HHMMSS.SS). This function is meant to be passed as a formatter to field_reader.

Parameters:

  • field (String)

    the value in the field to be checked

Returns:

  • (Time)

    The value in the field or nil



213
214
215
216
217
218
219
220
221
222
# File 'lib/nmea_plus/message/base.rb', line 213

def _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, 0, 0, hms[1].to_i, hms[2].to_i, hms[3].to_f, '+00:00')
  rescue
    nil
  end
end

#_string(field) ⇒ String

string or nil. This function is meant to be passed as a formatter to field_reader.

Parameters:

  • field (String)

    the value in the field to be checked

Returns:

  • (String)

    The value in the field or nil



179
180
181
182
# File 'lib/nmea_plus/message/base.rb', line 179

def _string(field)
  return nil if field.nil? || field.empty?
  field
end

#_utc_date_time(d_field, t_field) ⇒ Time

Create a Time object from a date and time field

Parameters:

  • d_field (String)

    the date value in the field to be checked

  • t_field (String)

    the time value in the field to be checked

Returns:

  • (Time)

    The value in the fields, or nil if either is not provided



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/nmea_plus/message/base.rb', line 228

def _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

#_utctime_hms(field) ⇒ Time

utc time or nil (HHMMSS or HHMMSS.SS). This function is meant to be passed as a formatter to field_reader.

Parameters:

  • field (String)

    the value in the field to be checked

Returns:

  • (Time)

    The value in the field or nil



197
198
199
200
201
202
203
204
205
206
207
# File 'lib/nmea_plus/message/base.rb', line 197

def _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

#add_message_part(msg) ⇒ void

This method returns an undefined value.

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)

Parameters:



101
102
103
104
105
106
107
# File 'lib/nmea_plus/message/base.rb', line 101

def add_message_part(msg)
  if @next_part.nil?
    @next_part = msg
  else
    @next_part.add_message_part(msg)
  end
end

#all_checksums_ok?Boolean

return [bool] Whether the checksums for all available message parts are OK

Returns:

  • (Boolean)


70
71
72
73
74
# File 'lib/nmea_plus/message/base.rb', line 70

def all_checksums_ok?
  return false unless checksum_ok?
  return true if @next_part.nil?
  @next_part.all_checksums_ok?
end

#all_messages_received?bool

Returns Whether all messages in a multipart message have been received.

Returns:

  • (bool)

    Whether all messages in a multipart message have been received.



110
111
112
# File 'lib/nmea_plus/message/base.rb', line 110

def all_messages_received?
  total_messages == highest_contiguous_index
end

#calculated_checksumObject

return [String] The calculated checksum for this payload as a two-character string



77
78
79
# File 'lib/nmea_plus/message/base.rb', line 77

def calculated_checksum
  "%02x" % @payload.each_byte.map(&:ord).reduce(:^)
end

#checksum_ok?bool

Returns Whether the checksum calculated from the payload matches the checksum given in the message.

Returns:

  • (bool)

    Whether the checksum calculated from the payload matches the checksum given in the message



65
66
67
# File 'lib/nmea_plus/message/base.rb', line 65

def checksum_ok?
  calculated_checksum.casecmp(checksum).zero?
end

#highest_contiguous_indexInteger

Returns The highest contiguous sequence number of linked message parts.

Returns:

  • (Integer)

    The highest contiguous sequence number of linked message parts

See Also:



117
118
119
# File 'lib/nmea_plus/message/base.rb', line 117

def highest_contiguous_index
  _highest_contiguous_index(0)
end