fit4ruby 3.7.0 → 3.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1867e2f6e32d331216b372024ba0607837bb5dc3baf7daad107b5a23d6465aa6
4
- data.tar.gz: 25f0423ccf5e68259f48d02f35400fa2a723a9f7209613f77eca2631d056f2d0
3
+ metadata.gz: 29f7e7354dfe1a0b9cfa30849b6131693deba24858f7cf6ec0745b923bcbd86a
4
+ data.tar.gz: 24aa3e37347db57f6e26078118bc20da45692097c395a21ad480702ebb66f0c6
5
5
  SHA512:
6
- metadata.gz: 05641db1523d5e8a9b8ab939d4b65def4e5a50ca6bbcae3fb1c727dc7d641cfeda6d7a5e892b38819b840b0a2222e0d775d20bf4f93ae4af6db2c79b4b6753b3
7
- data.tar.gz: f4b7d619de81444329009c7a188f08bd4684524ecc1654a57b77df147a0e22730658d57247e30729f619a0ab2c6fbd6fce796908df05ff2d409a06a909656298
6
+ metadata.gz: 2638207885da4f16185df8ef71d7999855b2f9199be60d1156727473a855a01039ce0fd36faaa7093696f4576247f78c208c2628df200da2d5761832ebc46142
7
+ data.tar.gz: a7fd64132dba6d6594e9ade153e1596d66ef7f47c7846f79ad7971240ac8b6dfb0b457474e80dcd369faadc88dc79fee17233a3c3cc4cfe2d40f40f01df28e47
data/fit4ruby.gemspec CHANGED
@@ -24,7 +24,7 @@ EOT
24
24
  spec.require_paths = ["lib"]
25
25
  spec.required_ruby_version = '>=2.0'
26
26
 
27
- spec.add_dependency('bindata', '=2.3.0')
27
+ spec.add_dependency('bindata', '~>2.4.8')
28
28
  spec.add_development_dependency('yard', '~>0.9.20')
29
29
  spec.add_development_dependency('rake', '~>12.0.0')
30
30
  spec.add_development_dependency('bundler', '>=1.6.4')
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # = Activity.rb -- Fit4Ruby - FIT file processing library for Ruby
5
5
  #
6
- # Copyright (c) 2014, 2015 by Chris Schlaeger <cs@taskjuggler.org>
6
+ # Copyright (c) 2014, 2015, 2020 by Chris Schlaeger <cs@taskjuggler.org>
7
7
  #
8
8
  # This program is free software; you can redistribute it and/or modify
9
9
  # it under the terms of version 2 of the GNU General Public License as
@@ -33,45 +33,65 @@ require 'fit4ruby/PersonalRecords'
33
33
 
34
34
  module Fit4Ruby
35
35
 
36
- # This is the most important class of this library. It holds references to
37
- # all other data structures. Each of the objects it references are direct
38
- # equivalents of the message record structures used in the FIT file.
36
+ # Activity files are arguably the most common type of FIT file. The Activity
37
+ # class represents the top-level structure of an activity FIT file.
38
+ # It holds references to all other data structures. Each of the objects it
39
+ # references are direct equivalents of the message record structures used in
40
+ # the FIT file.
39
41
  class Activity < FitDataRecord
40
42
 
41
- attr_accessor :file_id, :field_descriptions, :developer_data_ids, :epo_data,
42
- :file_creator, :device_infos, :sensor_settings, :data_sources,
43
- :user_data, :user_profiles, :physiological_metrics,
44
- :sessions, :laps, :records, :lengths, :hrv,
45
- :heart_rate_zones, :events, :personal_records
43
+ # These symbols are a complete list of all the sub-sections that an
44
+ # activity FIT file may contain. This list is used to generate accessors,
45
+ # instance variables and other sections of code. Some are just simple
46
+ # instance variables, but the majority can appear multiple times and hence
47
+ # are stored in an Array.
48
+ FILE_SECTIONS = [
49
+ :file_id,
50
+ :file_creator,
51
+ :events,
52
+ :device_infos,
53
+ :data_sources,
54
+ :epo_data,
55
+ :user_profiles,
56
+ :user_data,
57
+ :sensor_settings,
58
+ :developer_data_ids,
59
+ :field_descriptions,
60
+ :records,
61
+ :hrv,
62
+ :laps,
63
+ :lengths,
64
+ :heart_rate_zones,
65
+ :physiological_metrics,
66
+ :sessions,
67
+ :personal_records
68
+ ]
69
+
70
+ attr_accessor *FILE_SECTIONS
46
71
 
47
72
  # Create a new Activity object.
48
73
  # @param field_values [Hash] A Hash that provides initial values for
49
74
  # certain fields of the FitDataRecord.
50
75
  def initialize(field_values = {})
51
76
  super('activity')
52
- @meta_field_units['total_gps_distance'] = 'm'
53
- @num_sessions = 0
54
77
 
55
- @file_id = FileId.new
56
- @field_descriptions = []
57
- @developer_data_ids = []
78
+ # The variables hold references to other parts of the FIT file. These
79
+ # can either be direct references to a certain FIT file section or an
80
+ # Array in case the section can appear multiple times in the FIT file.
81
+ @file_id = new_file_id()
82
+ @file_creator = new_file_creator()
58
83
  @epo_data = nil
59
- @file_creator = FileCreator.new
60
- @device_infos = []
61
- @sensor_settings = []
62
- @data_sources = []
63
- @user_data = []
64
- @user_profiles = []
65
- @physiological_metrics = []
66
- @events = []
67
- @sessions = []
68
- @laps = []
69
- @lengths = []
70
- @records = []
71
- @hrv = []
72
- @heart_rate_zones = []
73
- @personal_records = []
84
+ # Initialize the remaining variables as empty Array.
85
+ FILE_SECTIONS.each do |fs|
86
+ ivar_name = '@' + fs.to_s
87
+ unless instance_variable_defined?(ivar_name)
88
+ instance_variable_set(ivar_name, [])
89
+ end
90
+ end
74
91
 
92
+ # The following variables hold derived or auxilliary information that
93
+ # are not directly part of the FIT file.
94
+ @meta_field_units['total_gps_distance'] = 'm'
75
95
  @cur_session_laps = []
76
96
 
77
97
  @cur_lap_records = []
@@ -99,11 +119,6 @@ module Fit4Ruby
99
119
  end
100
120
  @device_infos.each.with_index { |d, index| d.check(index) }
101
121
  @sensor_settings.each.with_index { |s, index| s.check(index) }
102
- unless @num_sessions == @sessions.count
103
- Log.fatal "Activity record requires #{@num_sessions}, but "
104
- "#{@sessions.length} session records were found in the "
105
- "FIT file."
106
- end
107
122
 
108
123
  # Records must have consecutively growing timestamps and distances.
109
124
  ts = Time.parse('1989-12-31')
@@ -308,13 +323,17 @@ module Fit4Ruby
308
323
  def write(io, id_mapper)
309
324
  @file_id.write(io, id_mapper)
310
325
  @file_creator.write(io, id_mapper)
326
+ @epo_data.write(io, id_mapper) if @epo_data
311
327
 
312
- (@field_descriptions + @developer_data_ids +
313
- @device_infos + @sensor_settings +
314
- @data_sources + @user_profiles +
315
- @physiological_metrics + @events +
316
- @sessions + @laps + @records + @lengths +
317
- @heart_rate_zones + @personal_records).sort.each do |s|
328
+ ary_ivars = []
329
+ FILE_SECTIONS.each do |fs|
330
+ ivar_name = '@' + fs.to_s
331
+ if (ivar = instance_variable_get(ivar_name)) && ivar.respond_to?(:sort)
332
+ ary_ivars += ivar
333
+ end
334
+ end
335
+
336
+ ary_ivars.sort.each do |s|
318
337
  s.write(io, id_mapper)
319
338
  end
320
339
  super
@@ -464,18 +483,17 @@ module Fit4Ruby
464
483
  # @return [TrueClass/FalseClass] true if both Activities are equal,
465
484
  # otherwise false.
466
485
  def ==(a)
467
- super(a) && @file_id == a.file_id &&
468
- @file_creator == a.file_creator &&
469
- @field_descriptions == a.field_descriptions &&
470
- @developer_data_ids == a.developer_data_ids &&
471
- @device_infos == a.device_infos &&
472
- @sensor_settings == a.sensor_settings &&
473
- @data_sources == a.data_sources &&
474
- @physiological_metrics == a.physiological_metrics &&
475
- @user_profiles == a.user_profiles &&
476
- @heart_rate_zones == a.heart_rate_zones &&
477
- @events == a.events &&
478
- @sessions == a.sessions && personal_records == a.personal_records
486
+ return false unless super(a)
487
+
488
+ FILE_SECTIONS.each do |fs|
489
+ ivar_name = '@' + fs.to_s
490
+ ivar = instance_variable_get(ivar_name)
491
+ a_ivar = a.instance_variable_get(ivar_name)
492
+
493
+ return false unless ivar == a_ivar
494
+ end
495
+
496
+ true
479
497
  end
480
498
 
481
499
  # Create a new FitDataRecord.
@@ -521,7 +539,6 @@ module Fit4Ruby
521
539
  # Ensure that all previous records have been assigned to a lap.
522
540
  record = create_new_lap(lap_field_values)
523
541
  end
524
- @num_sessions += 1
525
542
  @sessions << (record = Session.new(@cur_session_laps, @lap_counter,
526
543
  field_values))
527
544
  @cur_session_laps = []
@@ -530,7 +547,7 @@ module Fit4Ruby
530
547
  when 'length'
531
548
  record = create_new_length(field_values)
532
549
  when 'record'
533
- record = Record.new(field_values)
550
+ record = Record.new(self, field_values)
534
551
  @cur_lap_records << record
535
552
  @cur_length_records << record
536
553
  @records << record
@@ -547,10 +564,31 @@ module Fit4Ruby
547
564
  record
548
565
  end
549
566
 
567
+ def export
568
+ # Collect all records in a consistent order.
569
+ records = []
570
+ FILE_SECTIONS.each do |fs|
571
+ ivar_name = '@' + fs.to_s
572
+ ivar = instance_variable_get(ivar_name)
573
+
574
+ next unless ivar
575
+
576
+ if ivar.respond_to?(:sort) and ivar.respond_to?(:empty?)
577
+ records += ivar.sort unless ivar.empty?
578
+ else
579
+ records << ivar if ivar
580
+ end
581
+ end
582
+
583
+ records.map do |record|
584
+ record.export
585
+ end
586
+ end
587
+
550
588
  private
551
589
 
552
590
  def create_new_lap(field_values)
553
- lap = Lap.new(@cur_lap_records, @laps.last,
591
+ lap = Lap.new(self, @cur_lap_records, @laps.last,
554
592
  field_values,
555
593
  @length_counter, @cur_lap_lengths)
556
594
  lap.message_index = @lap_counter - 1
@@ -573,6 +611,7 @@ module Fit4Ruby
573
611
 
574
612
  length
575
613
  end
614
+
576
615
  end
577
616
 
578
617
  end
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = FitDataRecord.rb -- Fit4Ruby - FIT file processing library for Ruby
5
+ #
6
+ # Copyright (c) 2020 by Chris Schlaeger <cs@taskjuggler.org>
7
+ #
8
+ # This program is free software; you can redistribute it and/or modify
9
+ # it under the terms of version 2 of the GNU General Public License as
10
+ # published by the Free Software Foundation.
11
+ #
12
+
13
+ module Fit4Ruby
14
+
15
+ # Some FIT message field names conflict with BinData reserved names. We use
16
+ # this translation method to map the conflicting names to BinData compatible
17
+ # names.
18
+ module BDFieldNameTranslator
19
+
20
+ BD_DICT = {
21
+ 'array' => '_array',
22
+ 'type' => '_type'
23
+ }
24
+
25
+ def to_bd_field_name(name)
26
+ if (bd_name = BD_DICT[name])
27
+ return bd_name
28
+ end
29
+
30
+ name
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = FDR_DevField_Extension.rb -- Fit4Ruby - FIT file processing library for Ruby
5
+ #
6
+ # Copyright (c) 2020 by Chris Schlaeger <cs@taskjuggler.org>
7
+ #
8
+ # This program is free software; you can redistribute it and/or modify
9
+ # it under the terms of version 2 of the GNU General Public License as
10
+ # published by the Free Software Foundation.
11
+ #
12
+
13
+ module Fit4Ruby
14
+
15
+ # This module extends FitDataRecord derived classes in case they have
16
+ # developer fields.
17
+ module FDR_DevField_Extension
18
+
19
+ def create_dev_field_instance_variables
20
+ # Create instance variables for developer fields
21
+ @dev_field_descriptions = {}
22
+ @top_level_record.field_descriptions.each do |field_description|
23
+ # Only create instance variables if the FieldDescription is for this
24
+ # message number.
25
+ if field_description.native_mesg_num == @message.number
26
+ name = field_description.full_field_name(@top_level_record.
27
+ developer_data_ids)
28
+ create_instance_variable(name)
29
+ @dev_field_descriptions[name] = field_description
30
+ end
31
+ end
32
+ end
33
+
34
+ def each_developer_field
35
+ @top_level_record.field_descriptions.each do |field_description|
36
+ # Only create instance variables if the FieldDescription is for this
37
+ # message number.
38
+ if field_description.native_mesg_num == @message.number
39
+ name = field_description.full_field_name(@top_level_record.
40
+ developer_data_ids)
41
+ yield(field_description, instance_variable_get('@' + name))
42
+ end
43
+ end
44
+ end
45
+
46
+ def get_unit_by_name(name)
47
+ if /[A-Za-z_]+_[A-F0-9]{16}/ =~ name
48
+ @dev_field_descriptions[name].units
49
+ else
50
+ super
51
+ end
52
+ end
53
+
54
+ def export
55
+ message = super
56
+
57
+ each_developer_field do |field_description, ivar|
58
+ field = field_description.message.fields_by_number[
59
+ field_description.native_field_num]
60
+ fit_value = field.native_to_fit(ivar)
61
+ unless field.is_undefined?(fit_value)
62
+ fld = {
63
+ 'number' => field_description.native_field_num | 128,
64
+ 'value' => field.fit_to_native(fit_value),
65
+ #'human' => field.to_human(fit_value),
66
+ 'type' => field.type
67
+ }
68
+ fld['unit'] = field.opts[:unit] if field.opts[:unit]
69
+ fld['scale'] = field.opts[:scale] if field.opts[:scale]
70
+
71
+ message['fields'][field.name] = fld
72
+ end
73
+ end
74
+
75
+ message
76
+ end
77
+
78
+ end
79
+
80
+ end
81
+
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # = FieldDescription.rb -- Fit4Ruby - FIT file processing library for Ruby
5
5
  #
6
- # Copyright (c) 2017, 2018, 2019 by Chris Schlaeger <cs@taskjuggler.org>
6
+ # Copyright (c) 2017, 2018, 2019, 2020 by Chris Schlaeger <cs@taskjuggler.org>
7
7
  #
8
8
  # This program is free software; you can redistribute it and/or modify
9
9
  # it under the terms of version 2 of the GNU General Public License as
@@ -25,6 +25,24 @@ module Fit4Ruby
25
25
  def initialize(field_values = {})
26
26
  super('field_description')
27
27
  set_field_values(field_values)
28
+
29
+ @full_field_name = nil
30
+ end
31
+
32
+ def full_field_name(developer_data_ids)
33
+ return @full_field_name if @full_field_name
34
+
35
+ if @developer_data_index >=
36
+ developer_data_ids.size
37
+ Log.error "Developer data index #{@developer_data_index} is too large"
38
+ return
39
+ end
40
+
41
+ app_id = developer_data_ids[@developer_data_index].application_id
42
+ # Convert the byte array with the app ID into a 16 character hex string.
43
+ app_id_str = app_id.map { |i| '%02X' % i }.join('')
44
+ @full_field_name =
45
+ "#{@field_name.gsub(/[^A-Za-z0-9_]/, '_')}_#{app_id_str}"
28
46
  end
29
47
 
30
48
  def create_global_definition(fit_entity)
@@ -35,12 +53,6 @@ module Fit4Ruby
35
53
  return
36
54
  end
37
55
 
38
- if @developer_data_index >=
39
- fit_entity.top_level_record.developer_data_ids.size
40
- Log.error "Developer data index #{@developer_data_index} is too large"
41
- return
42
- end
43
-
44
56
  msg = messages[@native_mesg_num] ||
45
57
  messages.message(@native_mesg_num, gfm.name)
46
58
  unless (@fit_base_type_id & 0x7F) < FIT_TYPE_DEFS.size
@@ -48,10 +60,10 @@ module Fit4Ruby
48
60
  return
49
61
  end
50
62
 
51
- name = "_#{@developer_data_index}_#{@field_name}"
52
63
  # A fit file may include multiple definitions of the same field. We
53
64
  # ignore all subsequent definitions.
54
- return if msg.has_field?(name)
65
+ return if msg.has_field?(full_field_name(fit_entity.top_level_record.
66
+ developer_data_ids))
55
67
 
56
68
  options = {}
57
69
  options[:scale] = @scale if @scale
@@ -60,7 +72,7 @@ module Fit4Ruby
60
72
  options[:unit] = @units
61
73
  msg.field(@field_definition_number,
62
74
  FIT_TYPE_DEFS[@fit_base_type_id & 0x7F][1],
63
- name, options)
75
+ @full_field_name, options)
64
76
  end
65
77
 
66
78
  end
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # = FileId.rb -- Fit4Ruby - FIT file processing library for Ruby
5
5
  #
6
- # Copyright (c) 2014 by Chris Schlaeger <cs@taskjuggler.org>
6
+ # Copyright (c) 2014, 2020 by Chris Schlaeger <cs@taskjuggler.org>
7
7
  #
8
8
  # This program is free software; you can redistribute it and/or modify
9
9
  # it under the terms of version 2 of the GNU General Public License as
@@ -23,6 +23,7 @@ module Fit4Ruby
23
23
  @time_created = Time.at(Time.now.to_i)
24
24
  @manufacturer = 'development'
25
25
  @type = 'activity'
26
+ @product = 0
26
27
 
27
28
  set_field_values(field_values)
28
29
  end