fit4ruby 3.7.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,9 +21,9 @@ module Fit4Ruby
21
21
  [ 'uint16', 'uint16', 0xFFFF, 2 ],
22
22
  [ 'sint32', 'int32', 0x7FFFFFFF, 4 ],
23
23
  [ 'uint32', 'uint32', 0xFFFFFFFF, 4 ],
24
- [ 'string', 'string', '', 0 ],
24
+ [ 'string', 'string', 0, 1 ],
25
25
  [ 'float32', 'float', 0xFFFFFFFF, 4 ],
26
- [ 'float63', 'double', 0xFFFFFFFF, 4 ],
26
+ [ 'float64', 'double', 0xFFFFFFFFFFFFFFFF, 8 ],
27
27
  [ 'uint8z', 'uint8', 0, 1 ],
28
28
  [ 'uint16z', 'uint16', 0, 2 ],
29
29
  [ 'uint32z', 'uint32', 0, 4 ],
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # = GlobalFitMessage.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
@@ -21,7 +21,8 @@ module Fit4Ruby
21
21
  # the data fields of the message.
22
22
  class GlobalFitMessage
23
23
 
24
- attr_reader :name, :number, :fields_by_name, :fields_by_number
24
+ attr_reader :name, :number, :fields_by_name, :fields_by_number,
25
+ :field_values_by_name
25
26
 
26
27
  # The Field objects describe the name, type and optional attributes of a
27
28
  # FitMessage definition field. It also provides methods to convert field
@@ -38,8 +39,21 @@ module Fit4Ruby
38
39
  @opts = opts
39
40
  end
40
41
 
42
+ def is_undefined?(value)
43
+ value == FitDefinitionFieldBase.undefined_value(@type)
44
+ end
45
+
46
+ def is_string?
47
+ @type == 'string'
48
+ end
49
+
50
+ def is_array?
51
+ @opts[:array] == true
52
+ end
53
+
41
54
  def to_machine(value)
42
- return nil if value.nil?
55
+ return nil if value.nil? ||
56
+ value == FitDefinitionFieldBase.undefined_value(@type)
43
57
 
44
58
  if @opts.include?(:dict) &&
45
59
  (dict = GlobalFitDictionaries[@opts[:dict]])
@@ -51,6 +65,13 @@ module Fit4Ruby
51
65
  value *= 180.0 / 2147483648
52
66
  when 'date_time'
53
67
  value = fit_time_to_time(value)
68
+ when 'float'
69
+ if value >= 4294967295.0
70
+ return nil
71
+ end
72
+ end
73
+ if value.is_a?(Float) && value >= 4294967295.0
74
+ return nil
54
75
  end
55
76
  value /= @opts[:scale].to_f if @opts[:scale]
56
77
  value -= @opts[:offset] if @opts[:offset]
@@ -133,7 +154,7 @@ module Fit4Ruby
133
154
  value
134
155
  end
135
156
 
136
- def to_s(value)
157
+ def to_s(value = nil)
137
158
  return "[no value]" if value.nil?
138
159
 
139
160
  human_readable = to_human(value)
@@ -167,7 +188,7 @@ module Fit4Ruby
167
188
 
168
189
  def field(ref_value, type, name, opts = {})
169
190
  field = Field.new(type, name, opts)
170
- if ref_value.respond_to?('each')
191
+ if ref_value.respond_to?(:each)
171
192
  ref_value.each do |rv|
172
193
  @fields[rv] = field
173
194
  end
@@ -180,13 +201,13 @@ module Fit4Ruby
180
201
  # Select the alternative field based on the actual field values of the
181
202
  # FitMessageRecord.
182
203
  def select(field_values_by_name)
183
- unless (value = field_values_by_name[@ref_field])
204
+ unless (value_of_referenced_field = field_values_by_name[@ref_field])
184
205
  Log.fatal "The selection field #{@ref_field} for the alternative " +
185
206
  "field is undefined in global message #{@message.name}: " +
186
207
  field_values_by_name.inspect
187
208
  end
188
209
  @fields.each do |ref_value, field|
189
- return field if ref_value == value
210
+ return field if ref_value == value_of_referenced_field
190
211
  end
191
212
  return @fields[:default] if @fields[:default]
192
213
 
@@ -199,7 +220,7 @@ module Fit4Ruby
199
220
  # Create a new GlobalFitMessage definition.
200
221
  # @param name [String] name of the FIT message
201
222
  # @param number [Fixnum] global message number
202
- def initialize(name, number)
223
+ def initialize(name, number, field_values_by_name = {})
203
224
  @name = name
204
225
  @number = number
205
226
  # Field names must be unique. A name always matches a single Field.
@@ -207,13 +228,57 @@ module Fit4Ruby
207
228
  # Field numbers are not unique. A group of alternative fields shares the
208
229
  # same number and is stored as an AltField. Otherwise as Field.
209
230
  @fields_by_number = {}
231
+ # To generate the proper definition message we need to know the length
232
+ # of String and Array fields. This is only needed when writing FIT
233
+ # files.
234
+ @field_values_by_name = field_values_by_name
210
235
  end
211
236
 
212
237
  # Two GlobalFitMessage objects are considered equal if they have the same
213
- # number, name and list of named fields.
238
+ # number, name and list of named fields. In case they have String or Array
239
+ # values, they must have identical size.
214
240
  def ==(m)
215
- @number == m.number && @name == m.name &&
216
- @fields_by_name.keys.sort == m.fields_by_name.keys.sort
241
+ unless @number == m.number && @name == m.name &&
242
+ @fields_by_name.keys.sort == m.fields_by_name.keys.sort
243
+ return false
244
+ end
245
+
246
+ unless @field_values_by_name.size == m.field_values_by_name.size
247
+ return false
248
+ end
249
+
250
+ unless @field_values_by_name.empty?
251
+ @field_values_by_name.keys.each do |name|
252
+ a = @field_values_by_name[name]
253
+ b = m.field_values_by_name[name]
254
+ if a.class != b.class
255
+ return false
256
+ end
257
+ if a.is_a?(String)
258
+ if a.bytes.length != b.bytes.length
259
+ return false
260
+ end
261
+ elsif a.is_a?(Array)
262
+ if a.length != b.length
263
+ return false
264
+ end
265
+ end
266
+ end
267
+ end
268
+
269
+ true
270
+ end
271
+
272
+ def each_field(field_values)
273
+ @fields_by_number.each do |number, field|
274
+ if field.is_a?(AltField)
275
+ # For alternative fields, we need to look at the value of the
276
+ # selector field and pick the corresponding Field.
277
+ field = field.select(field_values)
278
+ end
279
+
280
+ yield(number, field)
281
+ end
217
282
  end
218
283
 
219
284
  # Define a new Field for this message definition.
@@ -255,7 +320,7 @@ module Fit4Ruby
255
320
  end
256
321
 
257
322
  def construct(field_values_by_name)
258
- gfm = GlobalFitMessage.new(@name, @number)
323
+ gfm = GlobalFitMessage.new(@name, @number, field_values_by_name)
259
324
 
260
325
  @fields_by_number.each do |number, field|
261
326
  if field.is_a?(AltField)
@@ -269,7 +334,7 @@ module Fit4Ruby
269
334
  gfm
270
335
  end
271
336
 
272
- def write(io, local_message_type, global_fit_message = self)
337
+ def write(io, local_message_type)
273
338
  header = FitRecordHeader.new
274
339
  header.normal = 0
275
340
  header.message_type = 1
@@ -278,7 +343,7 @@ module Fit4Ruby
278
343
 
279
344
  definition = FitDefinition.new
280
345
  definition.global_message_number = @number
281
- definition.setup(global_fit_message)
346
+ definition.setup(self, @field_values_by_name)
282
347
  definition.write(io)
283
348
  end
284
349
 
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # = GlobalFitMessages.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
@@ -1017,7 +1017,7 @@ module Fit4Ruby
1017
1017
  field 18, 'uint8z', 'undocumented_field_18'
1018
1018
  field 19, 'uint8z', 'undocumented_field_19'
1019
1019
  field 20, 'uint8z', 'undocumented_field_20'
1020
- field 21, 'uint16', 'undocumented_field_21'
1020
+ field 21, 'uint16', 'wheel_size', :unit => 'mm'
1021
1021
  field 25, 'uint16', 'undocumented_field_25'
1022
1022
  field 26, 'uint16', 'undocumented_field_26'
1023
1023
  field 27, 'uint8', 'undocumented_field_27'
data/lib/fit4ruby/Lap.rb CHANGED
@@ -12,24 +12,30 @@
12
12
 
13
13
  require 'fit4ruby/FitDataRecord'
14
14
  require 'fit4ruby/RecordAggregator'
15
+ require 'fit4ruby/FDR_DevField_Extension'
15
16
 
16
17
  module Fit4Ruby
17
18
 
18
19
  class Lap < FitDataRecord
19
20
 
20
21
  include RecordAggregator
22
+ include FDR_DevField_Extension
21
23
 
22
24
  attr_reader :records, :lengths
23
25
 
24
26
  # Create a new Lap object.
27
+ # @param top_level_record [FitDataRecord] Top level record that is Lap
28
+ # belongs to.
25
29
  # @param records [Array of Records] Records to associate with the Lap.
26
30
  # @param lengths [Array of Lengths] Lengths to associate with the Lap.
27
31
  # @param first_length_index [Fixnum] Index of the first Length in this Lap.
28
32
  # @param previous_lap [Lap] Previous Lap on same Session.
29
33
  # @param field_values [Hash] Hash that provides initial values for certain
30
34
  # fields.
31
- def initialize(records, previous_lap, field_values, first_length_index, lengths)
35
+ def initialize(top_level_record, records, previous_lap, field_values,
36
+ first_length_index, lengths)
32
37
  super('lap')
38
+ @top_level_record = top_level_record
33
39
  @lengths = lengths
34
40
  @meta_field_units['avg_stride_length'] = 'm'
35
41
  @records = records
@@ -51,6 +57,9 @@ module Fit4Ruby
51
57
  @total_elapsed_time = records.last.timestamp - @start_time
52
58
  end
53
59
 
60
+ # Create instance variables for developer fields
61
+ create_dev_field_instance_variables
62
+
54
63
  set_field_values(field_values)
55
64
  end
56
65
 
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # = Record.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
@@ -11,6 +11,7 @@
11
11
  #
12
12
 
13
13
  require 'fit4ruby/FitDataRecord'
14
+ require 'fit4ruby/FDR_DevField_Extension'
14
15
 
15
16
  module Fit4Ruby
16
17
 
@@ -18,13 +19,21 @@ module Fit4Ruby
18
19
  # of primary measurements that are associated with a certain timestamp.
19
20
  class Record < FitDataRecord
20
21
 
22
+ include FDR_DevField_Extension
23
+
21
24
  # Create a new Record object.
25
+ # @param fit_entity The FitEntity this record belongs to
22
26
  # @param field_values [Hash] Hash that provides initial values for certain
23
27
  # fields.
24
- def initialize(field_values = {})
28
+ def initialize(top_level_record, field_values = {})
25
29
  super('record')
30
+ @top_level_record = top_level_record
26
31
  @meta_field_units['pace'] = 'min/km'
27
32
  @meta_field_units['run_cadence'] = 'spm'
33
+
34
+ # Create instance variables for developer fields
35
+ create_dev_field_instance_variables
36
+
28
37
  set_field_values(field_values)
29
38
  end
30
39
 
@@ -1,4 +1,4 @@
1
1
  module Fit4Ruby
2
2
  # The version number of the library.
3
- VERSION = '3.7.0'
3
+ VERSION = '3.8.0'
4
4
  end
data/spec/FitFile_spec.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # = FitFile_spec.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
@@ -12,115 +12,147 @@
12
12
 
13
13
  $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
14
 
15
+ #require "super_diff/rspec"
15
16
  require 'fit4ruby'
16
17
 
17
18
  ENV['TZ'] = 'UTC'
18
19
 
19
20
  describe Fit4Ruby do
21
+ before(:each) do
22
+ File.delete(fit_file) if File.exist?(fit_file)
23
+ expect(File.exist?(fit_file)).to be false
24
+ end
25
+
26
+ after(:each) { File.delete(fit_file) }
27
+
20
28
  let(:fit_file) { 'test.fit' }
21
- let(:timestamp) { Time.now }
22
- let(:activity) do
23
- ts = timestamp
24
- a = Fit4Ruby::Activity.new
25
- a.total_timer_time = 30 * 60.0
26
- a.new_user_data({ :age => 33, :height => 1.78, :weight => 73.0,
27
- :gender => 'male', :activity_class => 4.0,
28
- :max_hr => 178 })
29
-
30
- a.new_event({ :event => 'timer', :event_type => 'start_time' })
31
- a.new_device_info({ :timestamp => ts,
32
- :device_index => 0, :manufacturer => 'garmin',
33
- :garmin_product => 'fenix3',
34
- :serial_number => 123456789 })
35
- a.new_device_info({ :timestamp => ts,
36
- :device_index => 1, :manufacturer => 'garmin',
37
- :garmin_product => 'fenix3_gps'})
38
- a.new_device_info({ :timestamp => ts,
39
- :device_index => 2, :manufacturer => 'garmin',
40
- :garmin_product => 'hrm_run',
41
- :battery_status => 'ok' })
42
- a.new_device_info({ :timestamp => ts,
43
- :device_index => 3, :manufacturer => 'garmin',
44
- :garmin_product => 'sdm4',
45
- :battery_status => 'ok' })
46
- a.new_data_sources({ :timestamp => ts, :distance => 1,
47
- :speed => 1, :cadence => 3, :elevation => 1,
48
- :heart_rate => 2 })
49
- laps = 0
50
- 0.upto(a.total_timer_time / 60) do |mins|
51
- ts += 60
52
- a.new_record({
53
- :timestamp => ts,
54
- :position_lat => 51.5512 - mins * 0.0008,
55
- :position_long => 11.647 + mins * 0.002,
56
- :distance => 200.0 * mins,
57
- :altitude => (100 + mins * 0.5).to_i,
58
- :speed => 3.1,
59
- :vertical_oscillation => 9 + mins * 0.02,
60
- :stance_time => 235.0 * mins * 0.01,
61
- :stance_time_percent => 32.0,
62
- :heart_rate => 140 + mins,
63
- :cadence => 75,
64
- :activity_type => 'running',
65
- :fractional_cadence => (mins % 2) / 2.0
66
- })
29
+ # Round the timestamp to seconds. This is what the file format can store. A
30
+ # higher resolution would create errors during comparions.
31
+ let(:timestamp) { Time.at(Time.now.to_i) }
32
+ let(:user_data) do
33
+ {
34
+ :age => 33, :height => 1.78, :weight => 73.0,
35
+ :gender => 'male', :activity_class => 4.0,
36
+ :max_hr => 178
37
+ }
38
+ end
39
+ let(:user_profile) do
40
+ {
41
+ :friendly_name => 'Fast Runner',
42
+ :gender => 'male',
43
+ :age => 33,
44
+ :height => 1.78,
45
+ :weight => 73.0,
46
+ :resting_heart_rate => 43
47
+ }
48
+ end
49
+ def device_info_fenix3(ts)
50
+ {
51
+ :timestamp => ts,
52
+ :device_index => 0, :manufacturer => 'garmin',
53
+ :garmin_product => 'fenix3',
54
+ :serial_number => 123456789
55
+ }
56
+ end
57
+ def device_info_fenix3_gps(ts)
58
+ {
59
+ :timestamp => ts,
60
+ :device_index => 1, :manufacturer => 'garmin',
61
+ :garmin_product => 'fenix3_gps'
62
+ }
63
+ end
64
+ def device_info_hrm_run(ts, bs = 'ok')
65
+ {
66
+ :timestamp => ts,
67
+ :device_index => 2, :manufacturer => 'garmin',
68
+ :garmin_product => 'hrm_run',
69
+ :battery_status => bs
70
+ }
71
+ end
72
+ def device_info_sdm4(ts)
73
+ {
74
+ :timestamp => timestamp,
75
+ :device_index => 3, :manufacturer => 'garmin',
76
+ :garmin_product => 'sdm4',
77
+ :battery_status => 'ok'
78
+ }
79
+ end
80
+
81
+ context 'running activity' do
82
+ let(:activity) do
83
+ ts = timestamp
84
+ a = Fit4Ruby::Activity.new
85
+ a.total_timer_time = 30 * 60.0
86
+ a.new_user_data(user_data)
87
+ a.new_user_profile(user_profile)
88
+
89
+ a.new_event({ :event => 'timer', :event_type => 'start_time' })
90
+ a.new_device_info(device_info_fenix3(ts))
91
+ a.new_device_info(device_info_fenix3_gps(ts))
92
+ a.new_device_info(device_info_hrm_run(ts))
93
+ a.new_device_info(device_info_sdm4(ts))
94
+ a.new_data_sources({ :timestamp => ts, :distance => 1,
95
+ :speed => 1, :cadence => 3, :elevation => 1,
96
+ :heart_rate => 2 })
97
+ laps = 0
98
+ 0.upto(a.total_timer_time / 60) do |mins|
99
+ ts += 60
100
+ a.new_record({
101
+ :timestamp => ts,
102
+ :position_lat => 51.5512 - mins * 0.0008,
103
+ :position_long => 11.647 + mins * 0.002,
104
+ :distance => 200.0 * mins,
105
+ :altitude => (100 + mins * 0.5).to_i,
106
+ :speed => 3.1,
107
+ :vertical_oscillation => 9 + mins * 0.02,
108
+ :stance_time => 235.0 * mins * 0.01,
109
+ :stance_time_percent => 32.0,
110
+ :heart_rate => 140 + mins,
111
+ :cadence => 75,
112
+ :activity_type => 'running',
113
+ :fractional_cadence => (mins % 2) / 2.0
114
+ })
67
115
 
68
- if mins > 0 && mins % 5 == 0
69
- a.new_lap({ :timestamp => ts, :sport => 'running',
70
- :message_index => laps, :total_cycles => 195 })
71
- laps += 1
116
+ if mins > 0 && mins % 5 == 0
117
+ a.new_lap({ :timestamp => ts, :sport => 'running',
118
+ :message_index => laps, :total_cycles => 195 })
119
+ laps += 1
120
+ end
72
121
  end
73
- end
74
- a.new_session({ :timestamp => ts, :sport => 'running' })
75
- a.new_event({ :timestamp => ts, :event => 'recovery_time',
76
- :event_type => 'marker',
77
- :recovery_time => 2160 })
78
- a.new_event({ :timestamp => ts, :event => 'vo2max',
79
- :event_type => 'marker', :vo2max => 52 })
80
- a.new_event({ :timestamp => ts, :event => 'timer',
81
- :event_type => 'stop_all' })
82
- ts += 1
83
- a.new_device_info({ :timestamp => ts,
84
- :device_index => 0, :manufacturer => 'garmin',
85
- :garmin_product => 'fenix3',
86
- :serial_number => 123456789 })
87
- a.new_device_info({ :timestamp => ts,
88
- :device_index => 1, :manufacturer => 'garmin',
89
- :garmin_product => 'fenix3_gps'})
90
- a.new_device_info({ :timestamp => ts,
91
- :device_index => 2, :manufacturer => 'garmin',
92
- :garmin_product => 'hrm_run',
93
- :battery_status => 'low' })
94
- a.new_device_info({ :timestamp => ts,
95
- :device_index => 3, :manufacturer => 'garmin',
96
- :garmin_product => 'sdm4',
97
- :battery_status => 'ok' })
98
- ts += 120
99
- a.new_event({ :timestamp => ts, :event => 'recovery_hr',
100
- :event_type => 'marker', :recovery_hr => 132 })
101
-
102
- a.aggregate
103
- a
104
- end
122
+ a.new_session({ :timestamp => ts, :sport => 'running' })
123
+ a.new_event({ :timestamp => ts, :event => 'recovery_time',
124
+ :event_type => 'marker',
125
+ :recovery_time => 2160 })
126
+ a.new_event({ :timestamp => ts, :event => 'vo2max',
127
+ :event_type => 'marker', :vo2max => 52 })
128
+ a.new_event({ :timestamp => ts, :event => 'timer',
129
+ :event_type => 'stop_all' })
130
+ ts += 1
131
+ a.new_device_info(device_info_fenix3(ts))
132
+ a.new_device_info(device_info_fenix3_gps(ts))
133
+ a.new_device_info(device_info_hrm_run(ts, 'low'))
134
+ a.new_device_info(device_info_sdm4(ts))
135
+ ts += 120
136
+ a.new_event({ :timestamp => ts, :event => 'recovery_hr',
137
+ :event_type => 'marker', :recovery_hr => 132 })
105
138
 
106
- before do
107
- File.delete(fit_file) if File.exist?(fit_file)
108
- expect(File.exist?(fit_file)).to be false
109
- end
139
+ a.aggregate
140
+ a
141
+ end
110
142
 
111
- after { File.delete(fit_file) }
143
+ it 'should write an Activity FIT file and read it back' do
144
+ Fit4Ruby.write(fit_file, activity)
145
+ expect(File.exist?(fit_file)).to be true
112
146
 
113
- it 'should write an Activity FIT file and read it back' do
114
- Fit4Ruby.write(fit_file, activity)
115
- expect(File.exist?(fit_file)).to be true
147
+ b = Fit4Ruby.read(fit_file)
148
+ expect(b.laps.count).to eq 6
149
+ expect(b.lengths.count).to eq 0
150
+ expect(b.export).to eq(activity.export)
151
+ end
116
152
 
117
- b = Fit4Ruby.read(fit_file)
118
- expect(b.laps.count).to eq 6
119
- expect(b.lengths.count).to eq 0
120
- expect(b.inspect).to eq(activity.inspect)
121
153
  end
122
154
 
123
- context 'activity with Lengths' do
155
+ context 'swimming activity' do
124
156
  let(:activity) do
125
157
  ts = timestamp
126
158
  laps = 0
@@ -128,9 +160,7 @@ describe Fit4Ruby do
128
160
  a = Fit4Ruby::Activity.new
129
161
 
130
162
  a.total_timer_time = 30 * 60.0
131
- a.new_device_info({ :timestamp => ts,
132
- :device_index => 0, :manufacturer => 'garmin',
133
- :serial_number => 123456789 })
163
+ a.new_device_info(device_info_fenix3(ts))
134
164
 
135
165
  0.upto(a.total_timer_time / 60) do |mins|
136
166
  ts += 60
@@ -144,6 +174,7 @@ describe Fit4Ruby do
144
174
  lengths += 1
145
175
  end
146
176
  end
177
+ a.aggregate
147
178
  a
148
179
  end
149
180
 
@@ -156,9 +187,76 @@ describe Fit4Ruby do
156
187
  expect(b.lengths.count).to eq 6
157
188
  expect(b.laps.select { |l| l.sport == 'swimming' }.count).to eq 6
158
189
  expect(b.lengths.select { |l| l.total_strokes == 45 }.count).to eq 6
159
- expect(b.inspect).to eq(activity.inspect)
190
+ expect(b.export).to eq(activity.export)
191
+ end
192
+
193
+ end
194
+
195
+ context 'activity with developer fields' do
196
+ let(:activity) do
197
+ ts = timestamp
198
+ laps = 0
199
+ lengths = 0
200
+ a = Fit4Ruby::Activity.new
201
+
202
+ a.total_timer_time = 30 * 60.0
203
+ a.new_device_info(device_info_fenix3(ts))
204
+ a.new_developer_data_id({
205
+ application_id: [ 24, 251, 44, 240, 26, 75, 67, 13,
206
+ 173, 102, 152, 140, 132, 116, 33, 244 ],
207
+ application_version: 77
208
+ })
209
+ a.new_field_description({
210
+ developer_data_index: 0,
211
+ field_definition_number: 0,
212
+ fit_base_type_id: 132,
213
+ field_name: 'Power',
214
+ units: 'Watts',
215
+ native_mesg_num: 20,
216
+ native_field_num: 7
217
+ })
218
+ a.new_data_sources({ :timestamp => ts, :distance => 1,
219
+ :speed => 1, :cadence => 3, :elevation => 1,
220
+ :heart_rate => 2 })
221
+ laps = 0
222
+ 0.upto(a.total_timer_time / 60) do |mins|
223
+ ts += 60
224
+ a.new_record({
225
+ :timestamp => ts,
226
+ :position_lat => 51.5512 - mins * 0.0008,
227
+ :position_long => 11.647 + mins * 0.002,
228
+ :distance => 200.0 * mins,
229
+ :altitude => (100 + mins * 0.5).to_i,
230
+ :speed => 3.1,
231
+ :vertical_oscillation => 9 + mins * 0.02,
232
+ :stance_time => 235.0 * mins * 0.01,
233
+ :stance_time_percent => 32.0,
234
+ :heart_rate => 140 + mins,
235
+ :cadence => 75,
236
+ :activity_type => 'running',
237
+ :fractional_cadence => (mins % 2) / 2.0,
238
+ :Power_18FB2CF01A4B430DAD66988C847421F4 => 240
239
+ })
240
+
241
+ if mins > 0 && mins % 5 == 0
242
+ a.new_lap({ :timestamp => ts, :sport => 'running',
243
+ :message_index => laps, :total_cycles => 195 })
244
+ laps += 1
245
+ end
246
+ end
247
+ a.aggregate
248
+ a
160
249
  end
161
250
 
251
+ it 'should write an Activity FIT file and read it back' do
252
+ Fit4Ruby.write(fit_file, activity)
253
+ expect(File.exist?(fit_file)).to be true
254
+
255
+ # This currently does not work as the saving of developer fields is not
256
+ # yet implemented.
257
+ #b = Fit4Ruby.read(fit_file)
258
+ #expect(b.export).to eq(activity.export)
259
+ end
162
260
  end
163
261
 
164
262
  end