fit4ruby 3.7.0 → 3.10.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: d40c0fc77ae8184b42aa80643dfb6b0f036b3eab7f37bdb8ee66b76a9feaafc5
4
+ data.tar.gz: 6e8ce1d3472e67a3c7ba58200fdf6b395e009349e68dca0618a92b3176f8ff19
5
5
  SHA512:
6
- metadata.gz: 05641db1523d5e8a9b8ab939d4b65def4e5a50ca6bbcae3fb1c727dc7d641cfeda6d7a5e892b38819b840b0a2222e0d775d20bf4f93ae4af6db2c79b4b6753b3
7
- data.tar.gz: f4b7d619de81444329009c7a188f08bd4684524ecc1654a57b77df147a0e22730658d57247e30729f619a0ab2c6fbd6fce796908df05ff2d409a06a909656298
6
+ metadata.gz: 366a5e5b4f55ee4918c822e26eda16d6381a1e872b8af68504283def72f096e00717dc68f9bd52403d8e6662c4e2aff83d12d26e201d568794bca407513b5bd8
7
+ data.tar.gz: a4eda75c90f65d26b9786570ee0fa07e578a3d627996170385411305b17a7a9697b043de70b335e485f3ca5780cdb0157506d34212ab0c54947c4111fb951fbf
data/README.md CHANGED
@@ -10,7 +10,7 @@ me comments and patches. It was developed to form the back-end of
10
10
 
11
11
  ## Supported Devices
12
12
 
13
- Tested devices: Garmin FR620
13
+ Tested devices: Garmin FR620, Fenix 3, Fenix 5, Fenix 5X, Fenix 6X
14
14
 
15
15
  Other Garmin devices that generate FIT files may work as well. Since I
16
16
  don't have any other devices, I can't add support for them.
data/fit4ruby.gemspec CHANGED
@@ -24,9 +24,9 @@ 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
- spec.add_development_dependency('rake', '~>12.0.0')
29
+ spec.add_development_dependency('rake', '~>13.0.3')
30
30
  spec.add_development_dependency('bundler', '>=1.6.4')
31
31
  spec.add_development_dependency('rspec', '>=3.8')
32
32
  spec.add_development_dependency('pry', '>=0.12')
@@ -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
@@ -30,48 +30,72 @@ require 'fit4ruby/HRV'
30
30
  require 'fit4ruby/HeartRateZones'
31
31
  require 'fit4ruby/Event'
32
32
  require 'fit4ruby/PersonalRecords'
33
+ require 'fit4ruby/Workout'
34
+ require 'fit4ruby/WorkoutStep'
33
35
 
34
36
  module Fit4Ruby
35
37
 
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.
38
+ # Activity files are arguably the most common type of FIT file. The Activity
39
+ # class represents the top-level structure of an activity FIT file.
40
+ # It holds references to all other data structures. Each of the objects it
41
+ # references are direct equivalents of the message record structures used in
42
+ # the FIT file.
39
43
  class Activity < FitDataRecord
40
44
 
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
45
+ # These symbols are a complete list of all the sub-sections that an
46
+ # activity FIT file may contain. This list is used to generate accessors,
47
+ # instance variables and other sections of code. Some are just simple
48
+ # instance variables, but the majority can appear multiple times and hence
49
+ # are stored in an Array.
50
+ FILE_SECTIONS = [
51
+ :file_id,
52
+ :file_creator,
53
+ :events,
54
+ :device_infos,
55
+ :data_sources,
56
+ :epo_data,
57
+ :user_profiles,
58
+ :user_data,
59
+ :workouts,
60
+ :workout_steps,
61
+ :sensor_settings,
62
+ :developer_data_ids,
63
+ :field_descriptions,
64
+ :records,
65
+ :hrv,
66
+ :laps,
67
+ :lengths,
68
+ :heart_rate_zones,
69
+ :physiological_metrics,
70
+ :sessions,
71
+ :personal_records
72
+ ]
73
+
74
+ attr_accessor *FILE_SECTIONS
46
75
 
47
76
  # Create a new Activity object.
48
77
  # @param field_values [Hash] A Hash that provides initial values for
49
78
  # certain fields of the FitDataRecord.
50
79
  def initialize(field_values = {})
51
80
  super('activity')
52
- @meta_field_units['total_gps_distance'] = 'm'
53
- @num_sessions = 0
54
81
 
55
- @file_id = FileId.new
56
- @field_descriptions = []
57
- @developer_data_ids = []
82
+ # The variables hold references to other parts of the FIT file. These
83
+ # can either be direct references to a certain FIT file section or an
84
+ # Array in case the section can appear multiple times in the FIT file.
85
+ @file_id = new_file_id()
86
+ @file_creator = new_file_creator()
58
87
  @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 = []
88
+ # Initialize the remaining variables as empty Array.
89
+ FILE_SECTIONS.each do |fs|
90
+ ivar_name = '@' + fs.to_s
91
+ unless instance_variable_defined?(ivar_name)
92
+ instance_variable_set(ivar_name, [])
93
+ end
94
+ end
74
95
 
96
+ # The following variables hold derived or auxilliary information that
97
+ # are not directly part of the FIT file.
98
+ @meta_field_units['total_gps_distance'] = 'm'
75
99
  @cur_session_laps = []
76
100
 
77
101
  @cur_lap_records = []
@@ -99,11 +123,6 @@ module Fit4Ruby
99
123
  end
100
124
  @device_infos.each.with_index { |d, index| d.check(index) }
101
125
  @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
126
 
108
127
  # Records must have consecutively growing timestamps and distances.
109
128
  ts = Time.parse('1989-12-31')
@@ -308,13 +327,17 @@ module Fit4Ruby
308
327
  def write(io, id_mapper)
309
328
  @file_id.write(io, id_mapper)
310
329
  @file_creator.write(io, id_mapper)
330
+ @epo_data.write(io, id_mapper) if @epo_data
331
+
332
+ ary_ivars = []
333
+ FILE_SECTIONS.each do |fs|
334
+ ivar_name = '@' + fs.to_s
335
+ if (ivar = instance_variable_get(ivar_name)) && ivar.respond_to?(:sort)
336
+ ary_ivars += ivar
337
+ end
338
+ end
311
339
 
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|
340
+ ary_ivars.sort.each do |s|
318
341
  s.write(io, id_mapper)
319
342
  end
320
343
  super
@@ -386,6 +409,22 @@ module Fit4Ruby
386
409
  new_fit_data_record('user_profile', field_values)
387
410
  end
388
411
 
412
+ # Add a new Workout to the Activity.
413
+ # @param field_values [Hash] A Hash that provides initial values for
414
+ # certain fields of the FitDataRecord.
415
+ # @return [UserProfile]
416
+ def new_workout(field_values = {})
417
+ new_fit_data_record('workout', field_values)
418
+ end
419
+
420
+ # Add a new WorkoutSet to the Activity.
421
+ # @param field_values [Hash] A Hash that provides initial values for
422
+ # certain fields of the FitDataRecord.
423
+ # @return [UserProfile]
424
+ def new_workout_set(field_values = {})
425
+ new_fit_data_record('workout_set', field_values)
426
+ end
427
+
389
428
  # Add a new PhysiologicalMetrics to the Activity.
390
429
  # @param field_values [Hash] A Hash that provides initial values for
391
430
  # certain fields of the FitDataRecord.
@@ -464,18 +503,17 @@ module Fit4Ruby
464
503
  # @return [TrueClass/FalseClass] true if both Activities are equal,
465
504
  # otherwise false.
466
505
  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
506
+ return false unless super(a)
507
+
508
+ FILE_SECTIONS.each do |fs|
509
+ ivar_name = '@' + fs.to_s
510
+ ivar = instance_variable_get(ivar_name)
511
+ a_ivar = a.instance_variable_get(ivar_name)
512
+
513
+ return false unless ivar == a_ivar
514
+ end
515
+
516
+ true
479
517
  end
480
518
 
481
519
  # Create a new FitDataRecord.
@@ -504,6 +542,10 @@ module Fit4Ruby
504
542
  @data_sources << (record = DataSources.new(field_values))
505
543
  when 'user_data'
506
544
  @user_data << (record = UserData.new(field_values))
545
+ when 'workout'
546
+ @workouts << (record = Workout.new(field_values))
547
+ when 'workout_step'
548
+ @workout_steps << (record = WorkoutStep.new(field_values))
507
549
  when 'user_profile'
508
550
  @user_profiles << (record = UserProfile.new(field_values))
509
551
  when 'physiological_metrics'
@@ -521,7 +563,6 @@ module Fit4Ruby
521
563
  # Ensure that all previous records have been assigned to a lap.
522
564
  record = create_new_lap(lap_field_values)
523
565
  end
524
- @num_sessions += 1
525
566
  @sessions << (record = Session.new(@cur_session_laps, @lap_counter,
526
567
  field_values))
527
568
  @cur_session_laps = []
@@ -530,7 +571,7 @@ module Fit4Ruby
530
571
  when 'length'
531
572
  record = create_new_length(field_values)
532
573
  when 'record'
533
- record = Record.new(field_values)
574
+ record = Record.new(self, field_values)
534
575
  @cur_lap_records << record
535
576
  @cur_length_records << record
536
577
  @records << record
@@ -547,10 +588,31 @@ module Fit4Ruby
547
588
  record
548
589
  end
549
590
 
591
+ def export
592
+ # Collect all records in a consistent order.
593
+ records = []
594
+ FILE_SECTIONS.each do |fs|
595
+ ivar_name = '@' + fs.to_s
596
+ ivar = instance_variable_get(ivar_name)
597
+
598
+ next unless ivar
599
+
600
+ if ivar.respond_to?(:sort) and ivar.respond_to?(:empty?)
601
+ records += ivar.sort unless ivar.empty?
602
+ else
603
+ records << ivar if ivar
604
+ end
605
+ end
606
+
607
+ records.map do |record|
608
+ record.export
609
+ end
610
+ end
611
+
550
612
  private
551
613
 
552
614
  def create_new_lap(field_values)
553
- lap = Lap.new(@cur_lap_records, @laps.last,
615
+ lap = Lap.new(self, @cur_lap_records, @laps.last,
554
616
  field_values,
555
617
  @length_counter, @cur_lap_lengths)
556
618
  lap.message_index = @lap_counter - 1
@@ -573,6 +635,7 @@ module Fit4Ruby
573
635
 
574
636
  length
575
637
  end
638
+
576
639
  end
577
640
 
578
641
  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