fit4ruby 0.0.1

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.
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = FitFileId.rb -- Fit4Ruby - FIT file processing library for Ruby
5
+ #
6
+ # Copyright (c) 2014 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
+ require 'fit4ruby/Converters'
14
+ require 'fit4ruby/FitDataRecord'
15
+
16
+ module Fit4Ruby
17
+
18
+ class FitFileId < FitDataRecord
19
+
20
+ def initialize
21
+ super('file_id')
22
+ @serial_number = 1234567890
23
+ @time_created = Time.now
24
+ @manufacturer = 'development'
25
+ @type = 'activity'
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = FitFilter.rb -- Fit4Ruby - FIT file processing library for Ruby
5
+ #
6
+ # Copyright (c) 2014 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
+ class FitFilter < Struct.new(:record_numbers, :record_indexes, :field_names,
16
+ :ignore_undef)
17
+
18
+ def initialize
19
+ super
20
+ self[:ignore_undef] = false
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = FitHeader.rb -- Fit4Ruby - FIT file processing library for Ruby
5
+ #
6
+ # Copyright (c) 2014 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
+ require 'bindata'
14
+ require 'fit4ruby/Log'
15
+
16
+ module Fit4Ruby
17
+
18
+ class FitHeader < BinData::Record
19
+
20
+ endian :little
21
+
22
+ uint8 :header_size, :initial_value => 14
23
+ uint8 :protocol_version, :initial_value => 16
24
+ uint16 :profile_version, :initial_value => 1012
25
+ uint32 :data_size, :initial_value => 0
26
+ string :data_type, :read_length => 4, :initial_value => '.FIT'
27
+ uint16 :crc, :initial_value => 0
28
+
29
+ def check
30
+ unless header_size == 14
31
+ Log.fatal { "Unsupported header size #{@header.header_size}" }
32
+ end
33
+ unless data_type == '.FIT'
34
+ Log.fata { "Unknown file type #{@header.data_type}" }
35
+ end
36
+ end
37
+
38
+ def dump
39
+ puts <<"EOT"
40
+ Fit File Header
41
+ Header Size: #{header_size}
42
+ Protocol Version: #{protocol_version}
43
+ Profile Version: #{profile_version}
44
+ Data Size: #{data_size}
45
+ EOT
46
+ end
47
+
48
+ def end_pos
49
+ header_size + data_size
50
+ end
51
+
52
+ end
53
+
54
+ end
55
+
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = FitMessageIdMapper.rb -- Fit4Ruby - FIT file processing library for Ruby
5
+ #
6
+ # Copyright (c) 2014 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
+ class FitMessageIdMapper
16
+
17
+ class Entry < Struct.new(:global_id, :last_use)
18
+ end
19
+
20
+ def initialize
21
+ @entries = Array.new(16, nil)
22
+ end
23
+
24
+ def add_global(id)
25
+ unless (slot = @entries.index { |e| e.nil? })
26
+ puts @entries.inspect
27
+ # No more free slots. We have to find the least recently used one.
28
+ slot = 0
29
+ 0.upto(15) do |i|
30
+ if i != slot && @entries[slot].last_use > @entries[i].last_use
31
+ slot = i
32
+ end
33
+ end
34
+ end
35
+ @entries[slot] = Entry.new(id, Time.now)
36
+
37
+ slot
38
+ end
39
+
40
+ def get_local(id)
41
+ 0.upto(15) do |i|
42
+ if (entry = @entries[i]) && entry.global_id == id
43
+ entry.last_use = Time.now
44
+ return i
45
+ end
46
+ end
47
+ nil
48
+ end
49
+
50
+ end
51
+
52
+ end
53
+
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = FitMessageRecord.rb -- Fit4Ruby - FIT file processing library for Ruby
5
+ #
6
+ # Copyright (c) 2014 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
+ require 'bindata'
14
+ require 'fit4ruby/Log'
15
+ require 'fit4ruby/GlobalFitMessage'
16
+
17
+ module Fit4Ruby
18
+
19
+ class FitMessageRecord
20
+
21
+ attr_reader :global_message_number, :name, :message_record
22
+
23
+ def initialize(definition)
24
+ @definition = definition
25
+ @global_message_number = definition.global_message_number.snapshot
26
+
27
+ if (@gfm = GlobalFitMessages[@global_message_number])
28
+ @name = @gfm.name
29
+ else
30
+ @name = "message#{@global_message_number}"
31
+ Log.warn { "Unknown global message number #{@global_message_number}" }
32
+ end
33
+ @message_record = produce(definition)
34
+ end
35
+
36
+ def read(io, activity, filter = nil, fields_dump = nil)
37
+ @message_record.read(io)
38
+
39
+ obj = case @name
40
+ when 'activity'
41
+ activity
42
+ when 'session'
43
+ activity.new_session
44
+ when 'record'
45
+ activity.new_record
46
+ else
47
+ nil
48
+ end
49
+
50
+ @definition.fields.each do |field|
51
+ value = @message_record[field.name].snapshot
52
+ obj.set(field.name, field.to_machine(value)) if obj
53
+ if filter && fields_dump &&
54
+ (filter.field_names.nil? ||
55
+ filter.field_names.include?(field.name)) &&
56
+ (value != field.undefined_value || !filter.ignore_undef)
57
+ fields_dump[field] = value
58
+ end
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def produce(definition)
65
+ fields = []
66
+ definition.fields.each do |field|
67
+ fields << [ field.type, field.name ]
68
+ end
69
+
70
+ BinData::Struct.new(:endian => definition.endian, :fields => fields)
71
+ end
72
+
73
+
74
+ end
75
+
76
+ end
77
+
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = FitRecord.rb -- Fit4Ruby - FIT file processing library for Ruby
5
+ #
6
+ # Copyright (c) 2014 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
+ require 'fit4ruby/Log'
14
+ require 'fit4ruby/FitRecordHeader'
15
+ require 'fit4ruby/FitDefinition'
16
+ require 'fit4ruby/FitMessageRecord'
17
+ require 'fit4ruby/FitFilter'
18
+ require 'fit4ruby/Activity'
19
+
20
+ module Fit4Ruby
21
+
22
+ class FitRecord
23
+
24
+ def initialize(definitions)
25
+ @definitions = definitions
26
+ @name = @number = @fields = nil
27
+ end
28
+
29
+ def read(io, activity, filter, record_counters)
30
+ header = FitRecordHeader.read(io)
31
+
32
+ if header.normal?
33
+ # process normal headers
34
+ local_message_type = header.local_message_type.snapshot
35
+ if header.message_type == 1
36
+ # process definition message
37
+ definition = FitDefinition.read(io)
38
+ @definitions[local_message_type] = FitMessageRecord.new(definition)
39
+ else
40
+ # process data message
41
+ definition = @definitions[local_message_type]
42
+ unless definition
43
+ Log.fatal "Undefined local message type: #{local_message_type}"
44
+ end
45
+ if filter
46
+ @number = @definitions[local_message_type].global_message_number
47
+ index = (record_counters[@number] += 1)
48
+ if (filter.record_numbers.nil? ||
49
+ filter.record_numbers.include?(@number)) &&
50
+ (filter.record_indexes.nil? ||
51
+ filter.record_indexes.include?(index))
52
+ @name = definition.name
53
+ @fields = {}
54
+ end
55
+ end
56
+ definition.read(io, activity, filter, @fields)
57
+ end
58
+ else
59
+ # process compressed timestamp header
60
+ time_offset = header.time_offset
61
+ Log.fatal "Support for compressed headers not yet implemented"
62
+ end
63
+
64
+ self
65
+ end
66
+
67
+ def dump
68
+ return unless @fields
69
+
70
+ puts "Message #{@number}: #{@name}" unless @fields.empty?
71
+ @fields.each do |field, value|
72
+ puts " [#{"%-7s" % field.type(true)}] #{field.name}: " +
73
+ "#{field.to_s(value)}"
74
+ end
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = FitRecordHeader.rb -- Fit4Ruby - FIT file processing library for Ruby
5
+ #
6
+ # Copyright (c) 2014 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
+ class FitRecordHeader < BinData::Record
16
+
17
+ bit1 :normal
18
+
19
+ bit1 :message_type, :onlyif => :normal?
20
+ bit2 :reserved, :onlyif => :normal?
21
+
22
+ choice :local_message_type, :selection => :normal do
23
+ bit4 0
24
+ bit2 1
25
+ end
26
+
27
+ bit5 :time_offset, :onlyif => :compressed?
28
+
29
+ def normal?
30
+ normal == 0
31
+ end
32
+
33
+ def compressed?
34
+ normal == 1
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = GlobalFitDictList.rb -- Fit4Ruby - FIT file processing library for Ruby
5
+ #
6
+ # Copyright (c) 2014 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
+ require 'fit4ruby/Log'
14
+
15
+ module Fit4Ruby
16
+
17
+ class GlobalFitDict
18
+
19
+ def initialize
20
+ @entries = {}
21
+ end
22
+
23
+ def entry(number, name)
24
+ if @entries.include?(number)
25
+ Log.fatal "Entry #{number} has already been defined"
26
+ end
27
+ @entries[number] = name
28
+ end
29
+
30
+ def name(number)
31
+ @entries[number]
32
+ end
33
+
34
+ def value_by_name(name)
35
+ @entries.invert[name]
36
+ end
37
+
38
+ end
39
+
40
+ class GlobalFitDictList
41
+
42
+ def initialize(&block)
43
+ @current_dict = nil
44
+ @dicts = {}
45
+ instance_eval(&block) if block_given?
46
+ end
47
+
48
+ def dict(name)
49
+ if @dicts.include?(name)
50
+ Log.fatal "Dictionary #{name} has already been defined"
51
+ end
52
+ @dicts[name] = @current_dict = GlobalFitDict.new
53
+ end
54
+
55
+ def entry(number, name)
56
+ unless @current_dict
57
+ Log.fatal "You must define a dictionary first"
58
+ end
59
+ @current_dict.entry(number, name)
60
+ end
61
+
62
+ def [](name)
63
+ @dicts[name]
64
+ end
65
+
66
+ end
67
+
68
+ end
@@ -0,0 +1,302 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = GlobalFitDictionaries.rb -- Fit4Ruby - FIT file processing library for Ruby
5
+ #
6
+ # Copyright (c) 2014 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
+ require 'fit4ruby/GlobalFitDictList'
14
+
15
+ module Fit4Ruby
16
+
17
+ GlobalFitDictionaries = GlobalFitDictList.new do
18
+
19
+ dict 'activity_type'
20
+ entry 0, 'generic'
21
+ entry 1, 'running'
22
+ entry 2, 'cycling'
23
+ entry 3, 'transition'
24
+ entry 4, 'fitness_equipment'
25
+ entry 5, 'swimming'
26
+ entry 6, 'walking'
27
+ entry 254, 'all'
28
+
29
+ dict 'battery_status'
30
+ entry 1, 'new'
31
+ entry 2, 'good'
32
+ entry 3, 'ok'
33
+ entry 4, 'low'
34
+ entry 5, 'critical'
35
+
36
+ dict 'device_type'
37
+ entry 1, 'antfs'
38
+ entry 11, 'bike_power'
39
+ entry 12, 'environment_sensor_legacy'
40
+ entry 13, 'multi_sport_speed_distance'
41
+ entry 14, 'control'
42
+ entry 17, 'fitness_equipment'
43
+ entry 18, 'blood_pressure'
44
+ entry 19, 'geocache_node'
45
+ entry 20, 'light_electric_vehicle'
46
+ entry 25, 'env_sensor'
47
+ entry 26, 'racquet'
48
+ entry 30, 'running_dynamics' # Just a guess
49
+ entry 119, 'weight_scale'
50
+ entry 120, 'heart_rate'
51
+ entry 121, 'bike_speed_cadence'
52
+ entry 122, 'bike_cadence'
53
+ entry 123, 'bike_speed'
54
+ entry 124, 'stride_speed_distance'
55
+
56
+ dict 'event'
57
+ entry 0, 'timer'
58
+ entry 3, 'workout'
59
+ entry 4, 'workout_step'
60
+ entry 5, 'power_down'
61
+ entry 6, 'power_up'
62
+ entry 7, 'off_course'
63
+ entry 8, 'session'
64
+ entry 9, 'lap'
65
+ entry 10, 'course_point'
66
+ entry 11, 'battery'
67
+ entry 12, 'virtual_partner_pace'
68
+ entry 13, 'hr_high_alert'
69
+ entry 14, 'hr_low_alert'
70
+ entry 15, 'speed_high_alert'
71
+ entry 16, 'speed_low_alert'
72
+ entry 17, 'cad_high_alert'
73
+ entry 18, 'cad_low_alert'
74
+ entry 19, 'power_high_alert'
75
+ entry 20, 'power_low_alert'
76
+ entry 21, 'recovery_hr'
77
+ entry 22, 'battery_low'
78
+ entry 23, 'time_duration_alert'
79
+ entry 24, 'distance_duration_alert'
80
+ entry 25, 'calorie_duration_alert'
81
+ entry 26, 'activity'
82
+ entry 27, 'fitness_equipment'
83
+ entry 28, 'length'
84
+ entry 32, 'user_marker'
85
+ entry 33, 'sport_point'
86
+ entry 36, 'calibration'
87
+ entry 37, 'vo2max' # guess
88
+ entry 38, 'recovery_time' # guess (in minutes)
89
+ entry 42, 'front_gear_change'
90
+ entry 43, 'rear_gear_change'
91
+
92
+ dict 'event_type'
93
+ entry 0, 'start_time'
94
+ entry 1, 'stop'
95
+ entry 2, 'consecutive_depreciated'
96
+ entry 3, 'marker'
97
+ entry 4, 'stop_all'
98
+ entry 5, 'begin_depreciated'
99
+ entry 6, 'end_depreciated'
100
+ entry 7, 'end_all_depreciated'
101
+ entry 8, 'stop_disable'
102
+ entry 9, 'stop_disable_all'
103
+
104
+ dict 'file'
105
+ entry 1, 'device'
106
+ entry 2, 'settings'
107
+ entry 3, 'sport'
108
+ entry 4, 'activity'
109
+ entry 5, 'workout'
110
+ entry 6, 'course_point'
111
+ entry 7, 'schedules'
112
+ entry 9, 'weight_scale'
113
+ entry 10, 'totals'
114
+ entry 11, 'goals'
115
+ entry 14, 'blood_pressure'
116
+ entry 15, 'monitoring_a'
117
+ entry 20, 'activity_summary'
118
+ entry 28, 'monitoring_daily'
119
+ entry 32, 'monitoring_b'
120
+
121
+ dict 'gender'
122
+ entry 0, 'female'
123
+ entry 1, 'male'
124
+
125
+ dict 'intensity'
126
+ entry 0, 'active'
127
+ entry 1, 'rest'
128
+ entry 2, 'warmup'
129
+ entry 3, 'cooldown'
130
+
131
+ dict 'lap_trigger'
132
+ entry 0, 'manual'
133
+ entry 1, 'time'
134
+ entry 2, 'distance'
135
+ entry 3, 'position_start'
136
+ entry 4, 'position_lap'
137
+ entry 5, 'position_waypoint'
138
+ entry 6, 'position_marked'
139
+ entry 7, 'session_end'
140
+ entry 8, 'fitness_equipment'
141
+
142
+ dict 'manufacturer'
143
+ entry 1, 'Garmin'
144
+ entry 2, 'garmin_fr405_antfs'
145
+ entry 3, 'zephyr'
146
+ entry 4, 'dayton'
147
+ entry 5, 'idt'
148
+ entry 6, 'srm'
149
+ entry 7, 'quarq'
150
+ entry 8, 'ibike'
151
+ entry 9, 'saris'
152
+ entry 10, 'spark_hk'
153
+ entry 11, 'tanita'
154
+ entry 12, 'echowell'
155
+ entry 13, 'dynastream_oem'
156
+ entry 14, 'nautilus'
157
+ entry 15, 'dynastream'
158
+ entry 16, 'timex'
159
+ entry 17, 'metrigear'
160
+ entry 18, 'xelic'
161
+ entry 19, 'beurer'
162
+ entry 20, 'cardiosport'
163
+ entry 21, 'a_and_d'
164
+ entry 22, 'hmm'
165
+ entry 23, 'suunto'
166
+ entry 24, 'thita_elektronik'
167
+ entry 25, 'gpulse'
168
+ entry 26, 'clean_mobile'
169
+ entry 27, 'pedal_brain'
170
+ entry 28, 'peaksware'
171
+ entry 29, 'saxonar'
172
+ entry 30, 'lemond_fitness'
173
+ entry 31, 'dexcom'
174
+ entry 32, 'wahoo_fitness'
175
+ entry 33, 'octane_fitness'
176
+ entry 34, 'archinoetics'
177
+ entry 35, 'the_hurt_box'
178
+ entry 36, 'citizen_systems'
179
+ entry 37, 'magellan'
180
+ entry 38, 'osynce'
181
+ entry 39, 'holux'
182
+ entry 40, 'concept2'
183
+ entry 42, 'one_giant_leap'
184
+ entry 43, 'ace_sensor'
185
+ entry 44, 'brim_brothers'
186
+ entry 45, 'xplova'
187
+ entry 46, 'perception_digital'
188
+ entry 47, 'bf1systems'
189
+ entry 48, 'pioneer'
190
+ entry 49, 'spantec'
191
+ entry 50, 'metalogics'
192
+ entry 51, '4iiiis'
193
+ entry 52, 'seiko_epson'
194
+ entry 53, 'seiko_epson_oem'
195
+ entry 54, 'ifor_powell'
196
+ entry 55, 'maxwell_guider'
197
+ entry 56, 'star_trac'
198
+ entry 57, 'breakaway'
199
+ entry 58, 'alatech_technology_ltd'
200
+ entry 59, 'mio_technology_europe'
201
+ entry 60, 'rotor'
202
+ entry 61, 'geonaute'
203
+ entry 62, 'id_bike'
204
+ entry 63, 'specialized'
205
+ entry 64, 'wtek'
206
+ entry 65, 'physical_enterprises'
207
+ entry 66, 'north_pole_engineering'
208
+ entry 67, 'bkool'
209
+ entry 68, 'cateye'
210
+ entry 69, 'stages_cycling'
211
+ entry 70, 'sigmasport'
212
+ entry 71, 'tomtom'
213
+ entry 72, 'peripedal'
214
+ entry 73, 'wattbike'
215
+ entry 74, 'moxy'
216
+ entry 77, 'ciclosport'
217
+ entry 78, 'powerbahn'
218
+ entry 79, 'acorn_projects_aps'
219
+ entry 80, 'lifebeam'
220
+ entry 81, 'bontrager'
221
+ entry 82, 'wellgo'
222
+ entry 83, 'scosche'
223
+ entry 84, 'magura'
224
+ entry 85, 'woodway'
225
+ entry 86, 'elite'
226
+ entry 255, 'development'
227
+ entry 5759, 'actigraphcorp'
228
+
229
+ dict 'session_trigger'
230
+ entry 0, 'activity_end'
231
+ entry 1, 'manual'
232
+ entry 2, 'auto_multi_sport'
233
+ entry 3, 'fitness_equipment'
234
+
235
+ dict 'sport'
236
+ entry 0, 'generic'
237
+ entry 1, 'running'
238
+ entry 2, 'cycling'
239
+ entry 3, 'transition'
240
+ entry 4, 'fitness_equipment'
241
+ entry 5, 'swimming'
242
+ entry 6, 'basketball'
243
+ entry 7, 'soccer'
244
+ entry 8, 'tennis'
245
+ entry 9, 'american_football'
246
+ entry 10, 'training'
247
+ entry 11, 'walking'
248
+ entry 12, 'cross_country_skiing'
249
+ entry 13, 'alpine_skiing'
250
+ entry 14, 'snowboarding'
251
+ entry 15, 'rowing'
252
+ entry 16, 'mountaineering'
253
+ entry 17, 'hiking'
254
+ entry 18, 'multisport'
255
+ entry 19, 'paddling'
256
+ entry 254, 'all'
257
+
258
+ dict 'sub_sport'
259
+ entry 0, 'generic'
260
+ entry 1, 'treadmill'
261
+ entry 2, 'street'
262
+ entry 3, 'trail'
263
+ entry 4, 'track'
264
+ entry 5, 'spin'
265
+ entry 6, 'indoor_cycling'
266
+ entry 7, 'road'
267
+ entry 8, 'mountain'
268
+ entry 9, 'downhill'
269
+ entry 10, 'recumbent'
270
+ entry 11, 'cyclocross'
271
+ entry 12, 'hand_cycling'
272
+ entry 13, 'track_cycling'
273
+ entry 14, 'indoor_rowing'
274
+ entry 15, 'elliptical'
275
+ entry 16, 'stair_climbing'
276
+ entry 17, 'lap_swimming'
277
+ entry 18, 'open_water'
278
+ entry 19, 'flexibility_training'
279
+ entry 20, 'strength_training'
280
+ entry 21, 'warm_up'
281
+ entry 22, 'match'
282
+ entry 23, 'exercise'
283
+ entry 24, 'challenge'
284
+ entry 25, 'indoor_skiing'
285
+ entry 26, 'cardio_training'
286
+ entry 254, 'all'
287
+
288
+ dict 'product'
289
+ entry 8, 'hrm_run_single_byte_product_id'
290
+ entry 1623, 'fr620'
291
+ entry 1632, 'fr220'
292
+ entry 1752, 'hrm_run'
293
+ entry 1928, 'fr620_japan'
294
+ entry 1929, 'fr620_china'
295
+ entry 1930, 'fr220_japan'
296
+ entry 1931 , 'fr220_china'
297
+ entry 10007, 'sdm4'
298
+
299
+ end
300
+
301
+ end
302
+