fit4ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+