fit4ruby 1.4.0 → 1.5.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
  SHA1:
3
- metadata.gz: fa9593056b40b9f74f20741669e00d1529b2f565
4
- data.tar.gz: 9f91309b08ec8e29dfda1e6ac54b4b1fd944ff0a
3
+ metadata.gz: 56efcdbb75bdc51a4076b705b5ec9e4a421b384f
4
+ data.tar.gz: d3e18fcccdd8ce2dc99b9c31f162fdad6e093491
5
5
  SHA512:
6
- metadata.gz: 7dec612eb68811672c4274237de0c75929035207d97c4ec0402f0209d86c907489dd26f64c46aa4241e7b2d6badcf92f92534e69cf2541d84acddce45a86e448
7
- data.tar.gz: cefae5b392d100eec3369b0f8ffd83836b14c2654853e43f0127577707a2a9b685da3ec84d6fab5b7694242947ea1db55ed99ed36d14d586434e06dc43678ddc
6
+ metadata.gz: 976290330339eb25fbd042b86954d4d8b83b45bcf0459a6fcfa73eca7ed1ec8fda5be43fa223cd7f12210098efeefa51ec25fb2e6ebfc6fc0cdb3dcddeff1bb6
7
+ data.tar.gz: 2e796850fe1332464e28694ed09f71fb1cfdb903764a1a5b8b0e05c6dad6d06b854965d3248832102625bdade88c3faff5f84837ef2e2e6674e8b0ea3b660f90
@@ -87,20 +87,43 @@ module Fit4Ruby
87
87
  # Records must have consecutively growing timestamps and distances.
88
88
  ts = Time.parse('1989-12-31')
89
89
  distance = nil
90
- @records.each do |r|
90
+ invalid_records = []
91
+ @records.each_with_index do |r, idx|
91
92
  Log.fatal "Record has no timestamp" unless r.timestamp
92
93
  if r.timestamp < ts
93
94
  Log.fatal "Record has earlier timestamp than previous record"
94
95
  end
95
96
  if r.distance
96
97
  if distance && r.distance < distance
97
- Log.fatal "Record #{r.timestamp} has smaller distance " +
98
- "(#{r.distance}) than an earlier record (#{distance})"
98
+ # Normally this should be a fatal error as the FIT file is clearly
99
+ # broken. Unfortunately, the Skiing/Boarding app in the Fenix3
100
+ # produces such broken FIT files. So we just warn about this
101
+ # problem and discard the earlier records.
102
+ Log.error "Record #{r.timestamp} has smaller distance " +
103
+ "(#{r.distance}) than an earlier record (#{distance})."
104
+ # Index of the list record to be discarded.
105
+ (idx - 1).downto(0) do |i|
106
+ if (ri = @records[i]).distance > r.distance
107
+ # This is just an approximation. It looks like the app adds
108
+ # records to the FIT file for runs that it meant to discard.
109
+ # Maybe the two successive time start events are a better
110
+ # criteria. But this workaround works for now.
111
+ invalid_records << ri
112
+ else
113
+ # All broken records have been found.
114
+ break
115
+ end
116
+ end
99
117
  end
100
118
  distance = r.distance
101
119
  end
102
120
  ts = r.timestamp
103
121
  end
122
+ unless invalid_records.empty?
123
+ # Delete all the broken records from the @records Array.
124
+ Log.warn "Discarding #{invalid_records.length} earlier records"
125
+ @records.delete_if { |r| invalid_records.include?(r) }
126
+ end
104
127
 
105
128
  # Laps must have a consecutively growing message index.
106
129
  @laps.each.with_index do |lap, index|
@@ -294,7 +317,7 @@ module Fit4Ruby
294
317
  # certain fields of the FitDataRecord.
295
318
  # @return [UserProfile]
296
319
  def new_user_profile(field_values = {})
297
- new_fit_data_record('user_profile', field_values)
320
+ new_fit_data_record('old_user_profile', field_values)
298
321
  end
299
322
 
300
323
  # Add a new Event to the Activity.
@@ -376,7 +399,7 @@ module Fit4Ruby
376
399
  @device_infos << (record = DeviceInfo.new(field_values))
377
400
  when 'data_sources'
378
401
  @data_sources << (record = DataSources.new(field_values))
379
- when 'user_profile'
402
+ when 'old_user_profile'
380
403
  @user_profiles << (record = UserProfile.new(field_values))
381
404
  when 'event'
382
405
  @events << (record = Event.new(field_values))
@@ -86,10 +86,7 @@ module Fit4Ruby
86
86
  v2 = field.fit_to_native(field.native_to_fit(
87
87
  fdr.instance_variable_get(ivar_name)))
88
88
 
89
- unless v1 == v2
90
- Log.error "#{name}: #{v1} != #{v2}"
91
- return false
92
- end
89
+ return false unless v1 == v2
93
90
  end
94
91
 
95
92
  true
@@ -36,31 +36,41 @@ module Fit4Ruby
36
36
  rescue StandardError => e
37
37
  Log.fatal "Cannot open FIT file '#{file_name}': #{e.message}"
38
38
  end
39
- header = FitHeader.read(io)
40
- header.check
41
-
42
- check_crc(io, io.size - 2)
43
-
44
- entity = FitFileEntity.new
45
- # This Array holds the raw data of the records that may be needed to
46
- # dump a human readable form of the FIT file.
47
- records = []
48
- # This hash will hold a counter for each record type. The counter is
49
- # incremented each time the corresponding record type is found.
50
- record_counters = Hash.new { 0 }
51
- while io.pos < header.end_pos
52
- record = FitRecord.new(definitions)
53
- record.read(io, entity, filter, record_counters)
54
- records << record if filter
39
+
40
+ entities = []
41
+ while !io.eof?
42
+ offset = io.pos
43
+
44
+ header = FitHeader.read(io)
45
+ header.check
46
+
47
+ check_crc(io, io.pos, offset + header.end_pos)
48
+
49
+ entity = FitFileEntity.new
50
+ # This Array holds the raw data of the records that may be needed to
51
+ # dump a human readable form of the FIT file.
52
+ records = []
53
+ # This hash will hold a counter for each record type. The counter is
54
+ # incremented each time the corresponding record type is found.
55
+ record_counters = Hash.new { 0 }
56
+ while io.pos < offset + header.end_pos
57
+ record = FitRecord.new(definitions)
58
+ record.read(io, entity, filter, record_counters)
59
+ records << record if filter
60
+ end
61
+ # Skip the 2 CRC bytes
62
+ io.seek(2, :CUR)
63
+
64
+ header.dump if filter && filter.record_numbers.nil?
65
+ dump_records(records) if filter
66
+
67
+ entity.check
68
+ entities << entity
55
69
  end
56
70
 
57
71
  io.close
58
72
 
59
- header.dump if filter && filter.record_numbers.nil?
60
- dump_records(records) if filter
61
-
62
- entity.check
63
- entity.top_level_record
73
+ entities[0].top_level_record
64
74
  end
65
75
 
66
76
  def write(file_name, top_level_record)
@@ -93,14 +103,11 @@ module Fit4Ruby
93
103
 
94
104
  private
95
105
 
96
- def check_crc(io, end_pos)
97
- # Save the current file IO position
98
- start_pos = io.pos
99
-
106
+ def check_crc(io, start_pos, end_pos)
100
107
  crc = compute_crc(io, start_pos, end_pos)
101
108
 
102
109
  # Read the 2 CRC bytes from the end of the file
103
- io.seek(-2, IO::SEEK_END)
110
+ io.seek(end_pos)
104
111
  crc_ref = io.readbyte.to_i | (io.readbyte.to_i << 8)
105
112
  io.seek(start_pos)
106
113
 
@@ -28,10 +28,10 @@ module Fit4Ruby
28
28
 
29
29
  def check
30
30
  unless header_size == 14
31
- Log.fatal { "Unsupported header size #{@header.header_size}" }
31
+ Log.fatal "Unsupported header size #{header_size}"
32
32
  end
33
33
  unless data_type == '.FIT'
34
- Log.fatal { "Unknown file type #{@header.data_type}" }
34
+ Log.fatal "Unknown file type #{data_type}"
35
35
  end
36
36
  end
37
37
 
@@ -28,6 +28,103 @@ module Fit4Ruby
28
28
  field 6, 'uint16', 'undocumented_field_6'
29
29
  field 7, 'uint32', 'undocumented_field_7'
30
30
 
31
+ message 2, 'device_settings'
32
+ field 0, 'uint8', 'active_time_zone'
33
+ field 1, 'uint32', 'utc_offset'
34
+ field 2, 'uint32', 'time_offset', :array => true
35
+ field 3, 'enum', 'undocumented_field_3'
36
+ field 4, 'enum', 'time_mode', :array => true
37
+ field 5, 'sint8', 'time_zone_offset', :array => true
38
+ field 8, 'uint16', 'undocumented_field_8'
39
+ field 9, 'enum', 'undocumented_field_9'
40
+ field 10, 'enum', 'undocumented_field_10'
41
+ field 11, 'enum', 'undocumented_field_11'
42
+ field 12, 'enum', 'backlight_mode'
43
+ field 13, 'uint8', 'undocumented_field_13'
44
+ field 14, 'uint8', 'undocumented_field_14'
45
+ field 15, 'uint8', 'undocumented_field_15'
46
+ field 22, 'enum', 'undocumented_field_22'
47
+ field 26, 'enum', 'undocumented_field_26'
48
+ field 28, 'enum', 'undocumented_field_28'
49
+ field 34, 'enum', 'undocumented_field_34'
50
+ field 35, 'enum', 'undocumented_field_35'
51
+ field 36, 'enum', 'activity_tracker_enabled'
52
+ field 38, 'enum', 'undocumented_field_38'
53
+ field 41, 'enum', 'undocumented_field_41'
54
+ field 42, 'enum', 'undocumented_field_42'
55
+ field 43, 'enum', 'undocumented_field_43'
56
+ field 44, 'enum', 'undocumented_field_44'
57
+ field 45, 'enum', 'undocumented_field_45'
58
+ field 46, 'enum', 'move_alert_enabled'
59
+ field 48, 'enum', 'undocumented_field_48'
60
+ field 49, 'uint16', 'undocumented_field_49'
61
+ field 51, 'enum', 'undocumented_field_51'
62
+ field 52, 'enum', 'undocumented_field_52'
63
+ field 53, 'enum', 'undocumented_field_53'
64
+ field 56, 'enum', 'mounting_side'
65
+ field 58, 'uint16', 'autosync_min_steps'
66
+ field 59, 'uint16', 'autosync_min_time'
67
+ field 62, 'enum', 'undocumented_field_62'
68
+ field 63, 'enum', 'undocumented_field_63'
69
+ field 64, 'enum', 'undocumented_field_64'
70
+ field 65, 'enum', 'undocumented_field_65'
71
+ field 66, 'enum', 'undocumented_field_66'
72
+ field 67, 'enum', 'undocumented_field_67'
73
+ field 68, 'enum', 'undocumented_field_68'
74
+ field 69, 'enum', 'undocumented_field_69'
75
+ field 80, 'enum', 'lactate_threshold_autodetect_enabled'
76
+ field 81, 'enum', 'undocumented_field_81'
77
+ field 82, 'enum', 'undocumented_field_82'
78
+ field 83, 'enum', 'undocumented_field_83'
79
+ field 84, 'enum', 'undocumented_field_84'
80
+ field 85, 'enum', 'undocumented_field_85'
81
+ field 87, 'enum', 'undocumented_field_87'
82
+ field 94, 'uint8', 'number_of_screens'
83
+
84
+ message 3, 'user_profile'
85
+ field 1, 'enum', 'gender', :dict => 'gender'
86
+ field 3, 'uint8', 'height'
87
+ field 4, 'uint16', 'weight'
88
+ field 5, 'enum', 'language'
89
+ field 6, 'enum', 'elev_setting'
90
+ field 7, 'enum', 'weight_setting'
91
+ field 8, 'uint8', 'resting_heart_rate'
92
+ field 12, 'enum', 'hr_setting'
93
+ field 13, 'enum', 'speed_setting'
94
+ field 14, 'enum', 'dist_setting'
95
+ field 17, 'enum', 'activity_class'
96
+ field 18, 'enum', 'position_setting'
97
+ field 21, 'enum', 'temperature_setting'
98
+ field 24, 'uint8', 'undocumented_field_24'
99
+ field 28, 'uint32', 'wake_time'
100
+ field 29, 'uint32', 'sleep_time'
101
+ field 30, 'enum', 'height_setting'
102
+ field 31, 'uint16', 'user_running_step_length'
103
+ field 32, 'uint16', 'user_walking_step_length'
104
+ field 33, 'uint16', 'undocumented_field_33'
105
+ field 34, 'uint16', 'undocumented_field_34'
106
+ field 35, 'uint32', 'undocumented_field_35'
107
+ field 36, 'uint8', 'undocumented_field_36'
108
+ field 37, 'uint16', 'undocumented_field_37'
109
+ field 38, 'uint16', 'undocumented_field_38'
110
+ field 39, 'float32', 'undocumented_field_39'
111
+ field 40, 'float32', 'undocumented_field_40'
112
+ field 41, 'uint32', 'undocumented_field_41'
113
+ field 42, 'uint32', 'undocumented_field_42'
114
+
115
+ message 7, 'zones_target'
116
+ field 1, 'uint8', 'max_heart_rate'
117
+ field 2, 'uint8', 'threshold_heart_rate'
118
+ field 3, 'uint16', 'functional_threshold_power'
119
+ field 5, 'enum', 'hr_zone_calc'
120
+ field 7, 'enum', 'pwr_zone_calc'
121
+ field 8, 'uint16', 'undocumented_field_8'
122
+ field 9, 'enum', 'undocumented_field_9'
123
+ field 10, 'enum', 'undocumented_field_10'
124
+ field 11, 'enum', 'undocumented_field_11'
125
+ field 12, 'enum', 'undocumented_field_12'
126
+ field 254, 'uint16', 'message_index'
127
+
31
128
  message 12, 'sport'
32
129
  field 0, 'enum', 'sport', :dict => 'sport'
33
130
  field 1, 'enum', 'sub_sport', :dict => 'sub_sport'
@@ -38,6 +135,45 @@ module Fit4Ruby
38
135
  field 10, 'uint8', 'undocumented_field_10', :array => true
39
136
  field 11, 'enum', 'undocumented_field_11'
40
137
 
138
+ # Not part of the official ANT SDK doc.
139
+ message 13, 'undocumented_13'
140
+ field 1, 'enum', 'undocumented_field_1'
141
+ field 2, 'uint16', 'undocumented_field_2'
142
+ field 3, 'enum', 'undocumented_field_3'
143
+ field 4, 'uint32', 'undocumented_field_4'
144
+ field 5, 'sint32', 'undocumented_field_5'
145
+ field 6, 'sint32', 'undocumented_field_6'
146
+ field 7, 'enum', 'undocumented_field_7'
147
+ field 8, 'uint16', 'undocumented_field_8'
148
+ field 11, 'uint8', 'undocumented_field_11'
149
+ field 12, 'enum', 'undocumented_field_12'
150
+ field 13, 'enum', 'undocumented_field_13'
151
+ field 14, 'enum', 'undocumented_field_14'
152
+ field 15, 'enum', 'undocumented_field_15'
153
+ field 16, 'enum', 'undocumented_field_16'
154
+ field 21, 'enum', 'undocumented_field_21'
155
+ field 22, 'uint16', 'undocumented_field_22'
156
+ field 23, 'enum', 'undocumented_field_23'
157
+ field 24, 'enum', 'undocumented_field_24'
158
+ field 27, 'enum', 'undocumented_field_27'
159
+ field 28, 'enum', 'undocumented_field_28'
160
+ field 29, 'enum', 'undocumented_field_29'
161
+ field 30, 'enum', 'undocumented_field_30'
162
+ field 31, 'uint32', 'undocumented_field_31'
163
+ field 32, 'uint16', 'undocumented_field_32'
164
+ field 33, 'uint32', 'undocumented_field_33'
165
+ field 34, 'enum', 'undocumented_field_34'
166
+ field 35, 'enum', 'undocumented_field_35'
167
+ field 36, 'enum', 'undocumented_field_36'
168
+ field 37, 'enum', 'undocumented_field_37'
169
+ field 38, 'uint8', 'undocumented_field_38'
170
+ field 39, 'uint8', 'undocumented_field_39'
171
+ field 40, 'enum', 'undocumented_field_40'
172
+ field 41, 'sint32', 'undocumented_field_41'
173
+ field 42, 'uint8', 'undocumented_field_42'
174
+ field 46, 'enum', 'undocumented_field_46'
175
+ field 254, 'uint16', 'message_index'
176
+
41
177
  message 18, 'session'
42
178
  field 0, 'enum', 'event', :dict => 'event'
43
179
  field 1, 'enum', 'event_type', :dict => 'event_type'
@@ -336,6 +472,7 @@ module Fit4Ruby
336
472
  field 21, 'uint16z', 'ant_device_number'
337
473
  field 22, 'enum', 'ant_network', :dict => 'ant_network'
338
474
  field 23, 'uint8', 'undocumented_field_23'
475
+ field 24, 'uint32z', 'undocumented_field_24'
339
476
  field 25, 'enum', 'source_type', :dict => 'source_type'
340
477
  field 253, 'uint32', 'timestamp', :type => 'date_time'
341
478
 
@@ -435,7 +572,7 @@ module Fit4Ruby
435
572
  field 0, 'uint16', 'time', :array => true, :scale => 1000, :unit => 's'
436
573
 
437
574
  # Not part of the official ANT SDK doc
438
- message 79, 'user_profile'
575
+ message 79, 'old_user_profile'
439
576
  field 0, 'uint16', 'metmax', :scale => 1000, :unit => 'MET' # VO2max / 3.5
440
577
  field 1, 'uint8', 'age', :unit => 'years'
441
578
  field 2, 'uint8', 'height', :scale => 100, :unit => 'm'
@@ -479,6 +616,8 @@ module Fit4Ruby
479
616
  field 5, 'uint16', 'resting_metabolic_rate', :unit => 'kcal/day'
480
617
  # Just a guess, not officially documented
481
618
  field 7, 'uint32', 'goal_cycles', :array => true
619
+ # Showed up in Fenix3HR firmware 3.84. Haven't seen a value yet.
620
+ field 8, 'enum', 'undocumented_field_8', :array => true
482
621
  field 253, 'uint32', 'timestamp', :type => 'date_time'
483
622
 
484
623
  # Not part of the official ANT SDK doc
@@ -539,11 +678,72 @@ module Fit4Ruby
539
678
  field 5, 'sint32', 'undocumented_field_5'
540
679
  field 253, 'uint32', 'timestamp', :type => 'date_time'
541
680
 
681
+ # Not part of the official ANT SDK doc.
682
+ message 147, 'undocumented_147'
683
+ field 0, 'uint32z', 'undocumented_field_0'
684
+ field 1, 'uint8', 'undocumented_field_1'
685
+ field 2, 'string', 'undocumented_field_2'
686
+ field 3, 'enum', 'undocumented_field_3'
687
+ field 4, 'enum', 'undocumented_field_4'
688
+ field 5, 'enum', 'undocumented_field_5'
689
+ field 6, 'enum', 'undocumented_field_6'
690
+ field 7, 'enum', 'undocumented_field_7'
691
+ field 8, 'uint8', 'undocumented_field_8'
692
+ field 9, 'uint8', 'undocumented_field_9'
693
+ field 10, 'uint16', 'undocumented_field_10'
694
+ field 11, 'uint16', 'undocumented_field_11'
695
+ field 12, 'uint8', 'undocumented_field_12'
696
+ field 13, 'uint32', 'undocumented_field_13'
697
+ field 14, 'uint8', 'undocumented_field_14'
698
+ field 15, 'uint8', 'undocumented_field_15'
699
+ field 16, 'uint8', 'undocumented_field_16'
700
+ field 17, 'uint8z', 'undocumented_field_17'
701
+ field 18, 'uint8z', 'undocumented_field_18'
702
+ field 19, 'uint8z', 'undocumented_field_19'
703
+ field 20, 'uint8z', 'undocumented_field_20'
704
+ field 21, 'uint16', 'undocumented_field_21'
705
+ field 24, 'enum', 'undocumented_field_24'
706
+ field 31, 'uint8', 'undocumented_field_31'
707
+ field 32, 'uint16', 'undocumented_field_32'
708
+ field 33, 'uint16', 'undocumented_field_33'
709
+ field 34, 'uint16', 'undocumented_field_34'
710
+ field 35, 'uint16', 'undocumented_field_35'
711
+ field 36, 'enum', 'undocumented_field_36'
712
+ field 37, 'enum', 'undocumented_field_37'
713
+ field 38, 'enum', 'undocumented_field_38'
714
+ field 39, 'enum', 'undocumented_field_39'
715
+ field 40, 'uint16z', 'undocumented_field_40'
716
+ field 41, 'uint8', 'undocumented_field_41'
717
+ field 42, 'enum', 'undocumented_field_42'
718
+ field 43, 'enum', 'undocumented_field_43'
719
+ field 44, 'uint8z', 'undocumented_field_44'
720
+ field 45, 'enum', 'undocumented_field_45'
721
+ field 46, 'enum', 'undocumented_field_46'
722
+ field 47, 'enum', 'undocumented_field_47'
723
+ field 254, 'uint16', 'message_index'
724
+
725
+ # Not part of the official ANT SDK doc.
542
726
  message 211, 'undocumented_211'
543
727
  field 0, 'uint8', 'undocumented_field_0'
544
728
  field 1, 'uint8', 'undocumented_field_1'
545
729
  field 253, 'uint32', 'timestamp', :type => 'date_time'
546
730
 
731
+ # Not part of the official ANT SDK doc.
732
+ message 216, 'undocumented_216'
733
+ field 0, 'uint16', 'undocumented_field_0'
734
+ field 1, 'uint16', 'undocumented_field_1'
735
+ field 2, 'uint32', 'undocumented_field_2'
736
+ field 5, 'uint32', 'undocumented_field_5'
737
+ field 6, 'uint8', 'undocumented_field_6'
738
+ field 9, 'uint16', 'undocumented_field_9'
739
+ field 10, 'enum', 'undocumented_field_10'
740
+ field 11, 'uint8', 'undocumented_field_11'
741
+ field 12, 'uint8', 'undocumented_field_12'
742
+ field 13, 'uint8', 'undocumented_field_13'
743
+ field 14, 'enum', 'undocumented_field_14'
744
+ field 15, 'uint16', 'undocumented_field_15'
745
+ field 253, 'uint32', 'timestamp', :type => 'date_time'
746
+
547
747
  end
548
748
 
549
749
  end
@@ -162,9 +162,10 @@ module Fit4Ruby
162
162
  end
163
163
  end
164
164
 
165
- @avg_stance_time = total_stance_time / stance_time_count
166
- @avg_stance_time_percent = total_stance_time_percent /
167
- stance_time_percent_count
165
+ @avg_stance_time = stance_time_count > 0 ?
166
+ total_stance_time / stance_time_count : 0
167
+ @avg_stance_time_percent = stance_time_percent_count > 0 ?
168
+ total_stance_time_percent / stance_time_percent_count : 0
168
169
  end
169
170
 
170
171
  end
@@ -14,14 +14,14 @@ require 'fit4ruby/FitDataRecord'
14
14
 
15
15
  module Fit4Ruby
16
16
 
17
- # This class corresponds to the user_profile FIT message.
17
+ # This class corresponds to the old_user_profile FIT message.
18
18
  class UserProfile < FitDataRecord
19
19
 
20
20
  # Create a new UserProfile object.
21
21
  # @param field_values [Hash] Hash that provides initial values for certain
22
22
  # fields.
23
23
  def initialize(field_values = {})
24
- super('user_profile')
24
+ super('old_user_profile')
25
25
  set_field_values(field_values)
26
26
  end
27
27
 
@@ -1,4 +1,4 @@
1
1
  module Fit4Ruby
2
2
  # The version number of the library.
3
- VERSION = '1.4.0'
3
+ VERSION = '1.5.0'
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fit4ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Schlaeger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-04 00:00:00.000000000 Z
11
+ date: 2017-02-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bindata