class F4R::Encoder::RegistryBuilder
{Encoder} requires a properly built {Registry} to be able to encode.
Attributes
registry[R]
@return [Registry]
Public Class Methods
new(records, source)
click to toggle source
# File lib/f4r.rb, line 1431 def initialize(records, source) @records = records @source = source source ? clone_definitions : build_definitions end
Private Instance Methods
build_definitions()
click to toggle source
Try to build definitions with the most accurate binary structure.
# File lib/f4r.rb, line 1488 def build_definitions @registry = Registry.new(Definition::Header.new) largest_records = @records. group_by { |record| record[:message_name] }. inject({}) do |r, rcrds| r[rcrds[0]] = rcrds[1].sort_by { |rf| rf[:fields].count }.last r end largest_records.each do |name, record| global_message = GlobalFit.messages.find do |m| m[:name] == name end definition = @registry.definition(record) unless definition record_header = Definition::RecordHeader.new record_header.normal = 0 record_header.message_type = 1 record_header.local_message_type = record[:local_message_number] definition = Definition::Record.new definition.field_count = record[:fields].count definition.global_message_number = global_message[:number] record[:fields].each_with_index do |(field_name, _), index| global_field = global_message[:fields]. find { |f| f[:field_name] == field_name } field_type = global_field[:field_type].to_sym global_type = GlobalFit.types[field_type] # Check in GlobalFit first as types can be anything form # strings to files, exercise, water, etc... base_type = GlobalFit.base_types.find do |dt| dt[:fit] == (global_type ? global_type[:base_type].to_sym : field_type) end unless base_type Log.warn <<~WARN Data type "#{field[:field_type]}" is not a valid type for field "#{field[:field_name]} (#{field[:filed_number]})". WARN end field = definition.data_fields[index] field.field_definition_number = global_field[:field_def] field.byte_count = 0 # set on build_records field.endian_ability = base_type[:endian] field.base_type_number = base_type[:number] end @registry.definitions << { local_message_number: record[:local_message_number], message_name: definition.global_message[:name], header: record_header, record: definition } end end build_records end
build_records()
click to toggle source
Build and add/fix records' binary data/format.
@return [Hash] fixed/validated records
# File lib/f4r.rb, line 1561 def build_records fixed_strings = {} @records.each do |record| definition = registry.definition(record) definition = definition && definition[:record] fields = {} definition.data_fields.each do |field| record_field = record[:fields][field.name] if record_field && !record_field[:value].nil? value = record_field[:value] else value = field.base_type_definition[:undef] sibling = field_sibling(field) if sibling.is_a?(Array) value = [field.base_type_definition[:undef]] * sibling.size end end unless from_source? field.byte_count = field.base_type_definition[:bytes] if field.base_type_definition[:bindata] == :string if fixed_strings[record[:message_name]] && fixed_strings[record[:message_name]][field.name] largest_string = fixed_strings[record[:message_name]][field.name] else largest_string = @records. select {|rd| rd[:message_name] == record[:message_name] }. map do |rd| rd[:fields][field.name] && rd[:fields][field.name][:value].to_s.length end.compact.sort.last fixed_strings[record[:message_name]] = {} fixed_strings[record[:message_name]][field.name] = largest_string end field.byte_count = ((largest_string / 8) * 8) + 8 end if value.is_a?(Array) field.byte_count *= value.size end end if field.base_type_definition[:bindata] == :string opts = {length: field.byte_count.snapshot} value = BinData::String.new(value, opts).snapshot end fields[field.name] = { value: value, base_type: field.base_type_definition, properties: field.global_message_field, definition: field } end registry.records << { message_name: definition.global_message[:name], message_number: definition.global_message[:number], local_message_number: record[:local_message_number], fields: fields } end end
clone_definitions()
click to toggle source
Decode source FIT file that will be used to provide binary structure for the FIT file to be created.
@param [String] source path to FIT file
# File lib/f4r.rb, line 1445 def clone_definitions io = ::File.open(@source, 'rb') begin until io.eof? offset = io.pos header = Definition::Header.read(io) @registry = Registry.new(header) while io.pos < offset + header.file_size record_header = Definition::RecordHeader.read(io) local_message_number = record_header.local_message_type.snapshot if record_header.for_new_definition? definition = Definition::Record.read(io) @registry.definitions << { local_message_number: local_message_number, message_name: definition.global_message[:name], header: record_header, record: definition, } else @registry.definitions.reverse.find do |d| d[:local_message_number] == local_message_number end[:record].read_data(io) end end io.seek(2, :CUR) end ensure io.close end build_records end
field_sibling(field)
click to toggle source
Helper method for finding a field's sibling.
This is mostly because we can't trust base_type on arrays.
@param [RecordField] field @return [Array,Integer,String] sibling
# File lib/f4r.rb, line 1641 def field_sibling(field) sibling = @records.find do |rd| rd[:message_name] == field.global_message[:name] && rd[:fields].keys.include?(field.name) end sibling && sibling[:fields][field.name][:value] end
from_source?()
click to toggle source
@return [Boolean]
# File lib/f4r.rb, line 1653 def from_source? !@source.nil? end