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



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

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



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

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



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

def fields
  @fields
end

#interpreted_data_typeString

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

Returns:



52
53
54
# File 'lib/nmea_plus/message/base.rb', line 52

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:



101
102
103
# File 'lib/nmea_plus/message/base.rb', line 101

def message_number
  1
end

#next_partNMEAPlus::Message

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

Returns:



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

def next_part
  @next_part
end

#originalString (readonly)

Returns The original message.

Returns:

  • (String)

    The original message



62
63
64
# File 'lib/nmea_plus/message/base.rb', line 62

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

#payloadString

Returns The unprocessed payload of the message.

Returns:

  • (String)

    The unprocessed payload of the message



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

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



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

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:



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

def total_messages
  1
end

Class 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



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

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



196
197
198
199
# File 'lib/nmea_plus/message/base.rb', line 196

def self._hex_to_integer(field)
  return nil if field.nil? || field.empty?
  field.hex
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



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

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



221
222
223
224
225
226
227
228
229
230
# File 'lib/nmea_plus/message/base.rb', line 221

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

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



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

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



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/nmea_plus/message/base.rb', line 236

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

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



205
206
207
208
209
210
211
212
213
214
215
# File 'lib/nmea_plus/message/base.rb', line 205

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

.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



146
147
148
149
150
151
152
# File 'lib/nmea_plus/message/base.rb', line 146

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.

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 `self.class_eval` the proper definition

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)



31
32
33
34
35
36
37
# File 'lib/nmea_plus/message/base.rb', line 31

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};self.class.#{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.



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

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

#_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:



133
134
135
136
137
138
# 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

#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:



109
110
111
112
113
114
115
# 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

#all_checksums_ok?Boolean

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

Returns:

  • (Boolean)


78
79
80
81
82
# File 'lib/nmea_plus/message/base.rb', line 78

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.



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

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



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

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



73
74
75
# File 'lib/nmea_plus/message/base.rb', line 73

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:



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

def highest_contiguous_index
  _highest_contiguous_index(0)
end