class Fit4Ruby::FitMessageRecord
The FitMessageRecord
models a part of the FIT file that contains the FIT message records. Each message record has a number, a local type and a set of data fields. The content of a FitMessageRecord
is defined by the FitDefinition
. This class is only used for reading data from a FIT file. For writing FIT message records, the class FitDataRecord
and its decendents are used.
Attributes
global_message_number[R]
message_record[R]
name[R]
Public Class Methods
new(definition)
click to toggle source
# File lib/fit4ruby/FitMessageRecord.rb, line 35 def initialize(definition) @definition = definition @global_message_number = definition.global_message_number.snapshot if (@gfm = GlobalFitMessages[@global_message_number]) @name = @gfm.name else @name = "message#{@global_message_number}" Log.debug { "Unknown global message number #{@global_message_number}" } end @message_record = produce(definition) end
Public Instance Methods
read(io, entity, filter = nil, fields_dump = nil, fit_entity)
click to toggle source
# File lib/fit4ruby/FitMessageRecord.rb, line 48 def read(io, entity, filter = nil, fields_dump = nil, fit_entity) @message_record.read(io) if @name == 'file_id' # Caveat: 'type' is used as '_type' in BinData fields! unless (entity_type = @message_record['_type'].snapshot) Log.fatal "Corrupted FIT file: file_id record has no type definition" end entity.set_type(entity_type) end obj = entity.new_fit_data_record(@name) # It's important to ensure that alternative fields are processed after # the regular fields so that the decision field has already been set. sorted_fields = @definition.data_fields.sort do |f1, f2| f1alt = is_alt_field?(f1) f2alt = is_alt_field?(f2) f1alt == f2alt ? f1.field_definition_number.snapshot <=> f2.field_definition_number.snapshot : f1alt ? 1 : -1 end sorted_fields.each do |field| value = @message_record[to_bd_field_name(field.name)].snapshot # Strings are null byte terminated. There may be more bytes in the # file, but we have to discard all bytes from the first null byte # onwards. if value.is_a?(String) if (null_byte = value.index("\0")) value = null_byte == 0 ? '' : value[0..(null_byte - 1)] end if value.empty? value = nil end end field_name, field_def = get_field_name_and_global_def(field, obj) obj.set(field_name, v = (field_def || field).to_machine(value)) if obj if filter && fields_dump && (filter.field_names.nil? || filter.field_names.include?(field_name)) && !(((value.is_a?(String) && (value.count(field.undefined_value.chr) == value.length)) || value == field.undefined_value) && filter.ignore_undef) fields_dump << DumpedField.new( @global_message_number, field.field_definition_number.snapshot, field_name, field.type(true), (field_def ? field_def : field).to_s(value)) end end @definition.developer_fields.each do |field| # Find the corresponding field description for the given developer and # field number. field_number = field.field_number.snapshot unless (field_description = field.find_field_definition) Log.error "There is no field definition for developer " + "#{field.developer_data_index} field #{field_number}" next end field_name = field_description.full_field_name(fit_entity) units = field_description.units type = field.type native_message_number = field_description.native_mesg_num native_field_number = field_description.native_field_num value = @message_record[field.name].snapshot value = nil if value == field.undefined_value obj.set(field_name, value) if obj if filter && fields_dump && (filter.field_names.nil? || filter.field_names.include?(field_name)) && !(((value.respond_to?('count') && (value.count(field.undefined_value) == value.length)) || value == field.undefined_value) && filter.ignore_undef) fields_dump << DumpedField.new( native_message_number, 256 * (1 + field.developer_data_index) + field_number, field_name, type, field.to_s(value)) end end if @name == 'field_description' obj.create_global_definition(fit_entity) end end
Private Instance Methods
get_field_name_and_global_def(field, obj)
click to toggle source
# File lib/fit4ruby/FitMessageRecord.rb, line 153 def get_field_name_and_global_def(field, obj) # If we don't have a corresponding GlobalFitMessage definition, we can't # tell if the field is an alternative or not. We don't treat it as such. return [ field.name, nil ] unless @gfm field_def_number = field.field_definition_number.snapshot # Get the corresponding GlobalFitMessage field definition. field_def = @gfm.fields_by_number[field_def_number] # If it's not an AltField, we just use the already given name. unless field_def.is_a?(GlobalFitMessage::AltField) return [ field.name, nil ] end # We have an AltField. Now we need to find the selection field and its # value. ref_field = field_def.ref_field ref_value = obj ? obj.get(ref_field) : :default # Based on that value, we select the Field of the AltField. selected_field = field_def.fields[ref_value] || field_def.fields[:default] Log.fatal "The value #{ref_value} of field #{ref_field} does not match " + "any selection of alternative field #{field_def_number} in " + "GlobalFitMessage #{@gfm.name}" unless selected_field [ selected_field.name, selected_field ] end
is_alt_field?(field)
click to toggle source
# File lib/fit4ruby/FitMessageRecord.rb, line 145 def is_alt_field?(field) return false unless @gfm field_def_number = field.field_definition_number.snapshot field_def = @gfm.fields_by_number[field_def_number] field_def.is_a?(GlobalFitMessage::AltField) end
produce(definition)
click to toggle source
# File lib/fit4ruby/FitMessageRecord.rb, line 181 def produce(definition) fields = [] (definition.data_fields.to_a + definition.developer_fields.to_a).each do |field| field_name = to_bd_field_name(field.name) field_def = [ field.type, field_name ] # Some field types need special handling. if field.type == 'string' # We need to also include the length of the String. field_def << { :read_length => field.total_bytes } elsif field.is_array? # For Arrays we have to break them into separte fields. field_def = [ :array, field_name, { :type => field.type.intern, :initial_length => field.total_bytes / field.base_type_bytes } ] end fields << field_def end BinData::Struct.new(:endian => definition.endian, :fields => fields) end