fit4ruby 1.6.1 → 1.6.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/fit4ruby.gemspec +3 -3
- data/lib/fit4ruby/Activity.rb +30 -2
- data/lib/fit4ruby/DeveloperDataId.rb +35 -0
- data/lib/fit4ruby/FieldDescription.rb +35 -0
- data/lib/fit4ruby/FitDataRecord.rb +2 -2
- data/lib/fit4ruby/FitDefinition.rb +29 -5
- data/lib/fit4ruby/FitDefinitionField.rb +4 -75
- data/lib/fit4ruby/FitDefinitionFieldBase.rb +105 -0
- data/lib/fit4ruby/FitDeveloperDataFieldDefinition.rb +42 -0
- data/lib/fit4ruby/FitMessageRecord.rb +18 -3
- data/lib/fit4ruby/FitRecord.rb +30 -31
- data/lib/fit4ruby/FitRecordHeader.rb +3 -3
- data/lib/fit4ruby/GlobalFitDictionaries.rb +19 -0
- data/lib/fit4ruby/GlobalFitMessage.rb +3 -3
- data/lib/fit4ruby/GlobalFitMessages.rb +54 -3
- data/lib/fit4ruby/version.rb +1 -1
- metadata +9 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 22fe9d584ae711b90ca8e963b254d959dd2e005c
|
4
|
+
data.tar.gz: 0b90e47c9f390ec90a8d5258638903222cd577a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 075fdc211b229dab82d43e32738edc57f6926a8eeee5f7128ea3863cd37b819ff4c064a296e3536aac6698c91a1641d716d7a243fdb60eebefd1ff002c8be92c
|
7
|
+
data.tar.gz: 1e9ad8de227dfcf75e5ca60614583a249eac8be43d3876bc85dd23e93fdea441983c75448d28c8216ddd56d6b70045e41b0042ee2da826d0c2575864880140bb
|
data/fit4ruby.gemspec
CHANGED
@@ -7,12 +7,12 @@ GEM_SPEC = Gem::Specification.new do |spec|
|
|
7
7
|
spec.name = 'fit4ruby'
|
8
8
|
spec.version = Fit4Ruby::VERSION
|
9
9
|
spec.license = 'GNU GPL version 2'
|
10
|
-
spec.summary = 'Library to read GARMIN FIT files.'
|
10
|
+
spec.summary = 'Library to read and write GARMIN FIT files.'
|
11
11
|
spec.description = <<EOT
|
12
12
|
This library can read and write FIT files and convert them into a Ruby data
|
13
13
|
structure for easy processing. This library was written for Garmin devices
|
14
|
-
like the FR620, Fenix 3
|
15
|
-
as well but have not been tested.
|
14
|
+
like the FR620, Fenix 3, Fenix 3 HR, Fenix 5 (s and X). Fit files from other
|
15
|
+
devices may work as well but have not been tested.
|
16
16
|
EOT
|
17
17
|
spec.authors = [ 'Chris Schlaeger' ]
|
18
18
|
spec.email = 'chris@linux.com'
|
data/lib/fit4ruby/Activity.rb
CHANGED
@@ -12,6 +12,8 @@
|
|
12
12
|
|
13
13
|
require 'fit4ruby/FitDataRecord'
|
14
14
|
require 'fit4ruby/FileId'
|
15
|
+
require 'fit4ruby/FieldDescription'
|
16
|
+
require 'fit4ruby/DeveloperDataId'
|
15
17
|
require 'fit4ruby/EPO_Data'
|
16
18
|
require 'fit4ruby/FileCreator'
|
17
19
|
require 'fit4ruby/DeviceInfo'
|
@@ -34,7 +36,7 @@ module Fit4Ruby
|
|
34
36
|
# equivalents of the message record structures used in the FIT file.
|
35
37
|
class Activity < FitDataRecord
|
36
38
|
|
37
|
-
attr_accessor :file_id, :epo_data,
|
39
|
+
attr_accessor :file_id, :field_descriptions, :developer_data_ids, :epo_data,
|
38
40
|
:file_creator, :device_infos, :sensor_settings, :data_sources,
|
39
41
|
:user_profiles, :physiological_metrics,
|
40
42
|
:sessions, :laps, :records, :hrv,
|
@@ -49,6 +51,8 @@ module Fit4Ruby
|
|
49
51
|
@num_sessions = 0
|
50
52
|
|
51
53
|
@file_id = FileId.new
|
54
|
+
@field_descriptions = []
|
55
|
+
@developer_data_ids = []
|
52
56
|
@epo_data = nil
|
53
57
|
@file_creator = FileCreator.new
|
54
58
|
@device_infos = []
|
@@ -282,7 +286,9 @@ module Fit4Ruby
|
|
282
286
|
@file_id.write(io, id_mapper)
|
283
287
|
@file_creator.write(io, id_mapper)
|
284
288
|
|
285
|
-
(@
|
289
|
+
(@field_descriptions + @developer_data_ids +
|
290
|
+
@device_infos + @sensor_settings +
|
291
|
+
@data_sources + @user_profiles +
|
286
292
|
@physiological_metrics + @events +
|
287
293
|
@sessions + @laps + @records + @heart_rate_zones +
|
288
294
|
@personal_records).sort.each do |s|
|
@@ -300,6 +306,22 @@ module Fit4Ruby
|
|
300
306
|
new_fit_data_record('file_id', field_values)
|
301
307
|
end
|
302
308
|
|
309
|
+
# Add a new FieldDescription to the Activity.
|
310
|
+
# @param field_values [Hash] A Hash that provides initial values for
|
311
|
+
# certain fields of the FitDataRecord.
|
312
|
+
# @return [FieldDescription]
|
313
|
+
def new_field_description(field_values = {})
|
314
|
+
new_fit_data_record('field_description', field_values)
|
315
|
+
end
|
316
|
+
|
317
|
+
# Add a new DeveloperDataId to the Activity.
|
318
|
+
# @param field_values [Hash] A Hash that provides initial values for
|
319
|
+
# certain fields of the FitDataRecord.
|
320
|
+
# @return [DeveloperDataId]
|
321
|
+
def new_field_description(field_values = {})
|
322
|
+
new_fit_data_record('developer_data_id', field_values)
|
323
|
+
end
|
324
|
+
|
303
325
|
# Add a new FileCreator to the Activity. It will replace any previously
|
304
326
|
# added FileCreator object.
|
305
327
|
# @param field_values [Hash] A Hash that provides initial values for
|
@@ -403,6 +425,8 @@ module Fit4Ruby
|
|
403
425
|
def ==(a)
|
404
426
|
super(a) && @file_id == a.file_id &&
|
405
427
|
@file_creator == a.file_creator &&
|
428
|
+
@field_descriptions == a.field_descriptions &&
|
429
|
+
@developer_data_ids == a.developer_data_ids &&
|
406
430
|
@device_infos == a.device_infos &&
|
407
431
|
@sensor_settings == a.sensor_settings &&
|
408
432
|
@data_sources == a.data_sources &&
|
@@ -423,6 +447,10 @@ module Fit4Ruby
|
|
423
447
|
case record_type
|
424
448
|
when 'file_id'
|
425
449
|
@file_id = (record = FileId.new(field_values))
|
450
|
+
when 'field_description'
|
451
|
+
@field_descriptions << (record = FieldDescription.new(field_values))
|
452
|
+
when 'developer_data_id'
|
453
|
+
@developer_data_ids << (record = DeveloperDataId.new(field_values))
|
426
454
|
when 'epo_data'
|
427
455
|
@epo_data = (record = EPO_Data.new(field_values))
|
428
456
|
when 'file_creator'
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
# encoding: UTF-8
|
3
|
+
#
|
4
|
+
# = DeveloperDataId.rb -- Fit4Ruby - FIT file processing library for Ruby
|
5
|
+
#
|
6
|
+
# Copyright (c) 2017 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/FitDataRecord'
|
14
|
+
|
15
|
+
module Fit4Ruby
|
16
|
+
|
17
|
+
# This class corresponds to the DeveloperDataId FIT message.
|
18
|
+
class DeveloperDataId < FitDataRecord
|
19
|
+
|
20
|
+
# Create a new DeveloperDataId object.
|
21
|
+
# @param field_values [Hash] Hash that provides initial values for certain
|
22
|
+
# fields.
|
23
|
+
def initialize(field_values = {})
|
24
|
+
super('developer_data_id')
|
25
|
+
set_field_values(field_values)
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_global_definition
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
# encoding: UTF-8
|
3
|
+
#
|
4
|
+
# = FieldDescription.rb -- Fit4Ruby - FIT file processing library for Ruby
|
5
|
+
#
|
6
|
+
# Copyright (c) 2017 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/FitDataRecord'
|
14
|
+
|
15
|
+
module Fit4Ruby
|
16
|
+
|
17
|
+
# This class corresponds to the FieldDescription FIT message.
|
18
|
+
class FieldDescription < FitDataRecord
|
19
|
+
|
20
|
+
# Create a new FieldDescription object.
|
21
|
+
# @param field_values [Hash] Hash that provides initial values for certain
|
22
|
+
# fields.
|
23
|
+
def initialize(field_values = {})
|
24
|
+
super('field_description')
|
25
|
+
set_field_values(field_values)
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_global_definition
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
|
@@ -131,7 +131,7 @@ module Fit4Ruby
|
|
131
131
|
# Create a BinData::Struct object to store the data record.
|
132
132
|
fields = []
|
133
133
|
global_fit_message.fields_by_number.each do |field_number, field|
|
134
|
-
bin_data_type =
|
134
|
+
bin_data_type = FitDefinitionFieldBase.fit_type_to_bin_data(field.type)
|
135
135
|
fields << [ bin_data_type, field.name ]
|
136
136
|
end
|
137
137
|
bd = BinData::Struct.new(:endian => :little, :fields => fields)
|
@@ -146,7 +146,7 @@ module Fit4Ruby
|
|
146
146
|
else
|
147
147
|
# If we don't have a corresponding variable or the variable is nil
|
148
148
|
# we write the 'undefined' value instead.
|
149
|
-
value =
|
149
|
+
value = FitDefinitionFieldBase.undefined_value(field.type)
|
150
150
|
end
|
151
151
|
bd[field.name] = value
|
152
152
|
end
|
@@ -12,6 +12,7 @@
|
|
12
12
|
|
13
13
|
require 'bindata'
|
14
14
|
require 'fit4ruby/FitDefinitionField'
|
15
|
+
require 'fit4ruby/FitDeveloperDataFieldDefinition'
|
15
16
|
|
16
17
|
module Fit4Ruby
|
17
18
|
|
@@ -24,6 +25,8 @@ module Fit4Ruby
|
|
24
25
|
# required.
|
25
26
|
class FitDefinition < BinData::Record
|
26
27
|
|
28
|
+
@@has_developer_data = false
|
29
|
+
|
27
30
|
hide :reserved
|
28
31
|
|
29
32
|
uint8 :reserved, :initial_value => 0
|
@@ -33,7 +36,14 @@ module Fit4Ruby
|
|
33
36
|
uint16be :default
|
34
37
|
end
|
35
38
|
uint8 :field_count
|
36
|
-
array :
|
39
|
+
array :data_fields, :type => FitDefinitionField,
|
40
|
+
:initial_length => :field_count
|
41
|
+
|
42
|
+
uint8 :developer_fields_count, :onlyif => :has_developer_data?
|
43
|
+
array :developer_fields,
|
44
|
+
:type => FitDeveloperDataFieldDefinition,
|
45
|
+
:initial_length => :developer_fields_count,
|
46
|
+
:onlyif => :has_developer_data?
|
37
47
|
|
38
48
|
def endian
|
39
49
|
architecture.snapshot == 0 ? :little : :big
|
@@ -43,7 +53,22 @@ module Fit4Ruby
|
|
43
53
|
if architecture.snapshot > 1
|
44
54
|
Log.fatal "Illegal architecture value #{architecture.snapshot}"
|
45
55
|
end
|
46
|
-
|
56
|
+
data_fields.each { |f| f.check }
|
57
|
+
end
|
58
|
+
|
59
|
+
def FitDefinition::read(io, entity, developer_data_flag)
|
60
|
+
@@has_developer_data = developer_data_flag != 0
|
61
|
+
super(io)
|
62
|
+
end
|
63
|
+
|
64
|
+
def FitDefinitionField::write(io)
|
65
|
+
# We don't support writing developer data fields yet.
|
66
|
+
@@has_developer_data = false
|
67
|
+
super(io)
|
68
|
+
end
|
69
|
+
|
70
|
+
def has_developer_data?
|
71
|
+
@@has_developer_data
|
47
72
|
end
|
48
73
|
|
49
74
|
def setup(fit_message_definition)
|
@@ -52,13 +77,12 @@ module Fit4Ruby
|
|
52
77
|
fdf.field_definition_number = number
|
53
78
|
fdf.set_type(f.type)
|
54
79
|
|
55
|
-
|
80
|
+
data_fields << fdf
|
56
81
|
end
|
57
|
-
self.field_count =
|
82
|
+
self.field_count = data_fields.length
|
58
83
|
end
|
59
84
|
|
60
85
|
end
|
61
86
|
|
62
|
-
|
63
87
|
end
|
64
88
|
|
@@ -12,6 +12,7 @@
|
|
12
12
|
|
13
13
|
require 'bindata'
|
14
14
|
require 'time'
|
15
|
+
require 'fit4ruby/FitDefinitionFieldBase'
|
15
16
|
require 'fit4ruby/Log'
|
16
17
|
require 'fit4ruby/GlobalFitMessage'
|
17
18
|
|
@@ -24,23 +25,7 @@ module Fit4Ruby
|
|
24
25
|
# the exact size of the binary records.
|
25
26
|
class FitDefinitionField < BinData::Record
|
26
27
|
|
27
|
-
|
28
|
-
# FIT Type, BinData type, undefined value, bytes
|
29
|
-
[ 'enum', 'uint8', 0xFF, 1 ],
|
30
|
-
[ 'sint8', 'int8', 0x7F, 1 ],
|
31
|
-
[ 'uint8', 'uint8', 0xFF, 1 ],
|
32
|
-
[ 'sint16', 'int16', 0x7FFF, 2 ],
|
33
|
-
[ 'uint16', 'uint16', 0xFFFF, 2 ],
|
34
|
-
[ 'sint32', 'int32', 0x7FFFFFFF, 4 ],
|
35
|
-
[ 'uint32', 'uint32', 0xFFFFFFFF, 4 ],
|
36
|
-
[ 'string', 'string', '', 0 ],
|
37
|
-
[ 'float32', 'float', 0xFFFFFFFF, 4 ],
|
38
|
-
[ 'float63', 'double', 0xFFFFFFFF, 4 ],
|
39
|
-
[ 'uint8z', 'uint8', 0, 1 ],
|
40
|
-
[ 'uint16z', 'uint16', 0, 2 ],
|
41
|
-
[ 'uint32z', 'uint32', 0, 4 ],
|
42
|
-
[ 'byte', 'uint8', 0xFF, 1 ]
|
43
|
-
]
|
28
|
+
include FitDefinitionFieldBase
|
44
29
|
|
45
30
|
hide :reserved
|
46
31
|
|
@@ -50,18 +35,6 @@ module Fit4Ruby
|
|
50
35
|
bit2 :reserved
|
51
36
|
bit5 :base_type_number
|
52
37
|
|
53
|
-
def self.fit_type_to_bin_data(fit_type)
|
54
|
-
entry = @@TypeDefs.find { |e| e[0] == fit_type }
|
55
|
-
raise "Unknown fit type #{fit_type}" unless entry
|
56
|
-
entry[1]
|
57
|
-
end
|
58
|
-
|
59
|
-
def self.undefined_value(fit_type)
|
60
|
-
entry = @@TypeDefs.find { |e| e[0] == fit_type }
|
61
|
-
raise "Unknown fit type #{fit_type}" unless entry
|
62
|
-
entry[2]
|
63
|
-
end
|
64
|
-
|
65
38
|
def init
|
66
39
|
@global_message_number = parent.parent.global_message_number.snapshot
|
67
40
|
@global_message_definition = GlobalFitMessages[@global_message_number]
|
@@ -72,7 +45,8 @@ module Fit4Ruby
|
|
72
45
|
"choice_#{field_number}"
|
73
46
|
@type = field.respond_to?('type') ? field.type : nil
|
74
47
|
|
75
|
-
if @type && (td = @@TypeDefs[
|
48
|
+
if @type && (td = @@TypeDefs[checked_base_type_number]) &&
|
49
|
+
td[0] != @type
|
76
50
|
Log.warn "#{@global_message_number}:#{@name} must be of type " +
|
77
51
|
"#{@type}, not #{td[0]}"
|
78
52
|
end
|
@@ -129,51 +103,6 @@ module Fit4Ruby
|
|
129
103
|
end
|
130
104
|
end
|
131
105
|
|
132
|
-
def set_type(fit_type)
|
133
|
-
idx = @@TypeDefs.index { |x| x[0] == fit_type }
|
134
|
-
raise "Unknown type #{fit_type}" unless idx
|
135
|
-
self.base_type_number = idx
|
136
|
-
self.byte_count = @@TypeDefs[idx][3]
|
137
|
-
end
|
138
|
-
|
139
|
-
def type(fit_type = false)
|
140
|
-
check_fit_base_type
|
141
|
-
@@TypeDefs[base_type_number.snapshot][fit_type ? 0 : 1]
|
142
|
-
end
|
143
|
-
|
144
|
-
def is_array?
|
145
|
-
if total_bytes > base_type_bytes
|
146
|
-
if total_bytes % base_type_bytes != 0
|
147
|
-
Log.fatal "Total bytes (#{total_bytes}) must be multiple of " +
|
148
|
-
"base type bytes (#{base_type_bytes})."
|
149
|
-
end
|
150
|
-
return true
|
151
|
-
end
|
152
|
-
false
|
153
|
-
end
|
154
|
-
|
155
|
-
def total_bytes
|
156
|
-
self.byte_count.snapshot
|
157
|
-
end
|
158
|
-
|
159
|
-
def base_type_bytes
|
160
|
-
check_fit_base_type
|
161
|
-
@@TypeDefs[base_type_number.snapshot][3]
|
162
|
-
end
|
163
|
-
|
164
|
-
def undefined_value
|
165
|
-
check_fit_base_type
|
166
|
-
@@TypeDefs[base_type_number.snapshot][2]
|
167
|
-
end
|
168
|
-
|
169
|
-
private
|
170
|
-
|
171
|
-
def check_fit_base_type
|
172
|
-
if @@TypeDefs.length <= base_type_number.snapshot
|
173
|
-
Log.fatal "Unknown FIT Base type #{base_type_number.snapshot}"
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
106
|
end
|
178
107
|
|
179
108
|
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
# encoding: UTF-8
|
3
|
+
#
|
4
|
+
# = FitDefinitionFieldBase.rb -- Fit4Ruby - FIT file processing library for Ruby
|
5
|
+
#
|
6
|
+
# Copyright (c) 2014, 2017 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
|
+
module FitDefinitionFieldBase
|
16
|
+
|
17
|
+
@@TypeDefs = [
|
18
|
+
# FIT Type, BinData type, undefined value, bytes
|
19
|
+
[ 'enum', 'uint8', 0xFF, 1 ],
|
20
|
+
[ 'sint8', 'int8', 0x7F, 1 ],
|
21
|
+
[ 'uint8', 'uint8', 0xFF, 1 ],
|
22
|
+
[ 'sint16', 'int16', 0x7FFF, 2 ],
|
23
|
+
[ 'uint16', 'uint16', 0xFFFF, 2 ],
|
24
|
+
[ 'sint32', 'int32', 0x7FFFFFFF, 4 ],
|
25
|
+
[ 'uint32', 'uint32', 0xFFFFFFFF, 4 ],
|
26
|
+
[ 'string', 'string', '', 0 ],
|
27
|
+
[ 'float32', 'float', 0xFFFFFFFF, 4 ],
|
28
|
+
[ 'float63', 'double', 0xFFFFFFFF, 4 ],
|
29
|
+
[ 'uint8z', 'uint8', 0, 1 ],
|
30
|
+
[ 'uint16z', 'uint16', 0, 2 ],
|
31
|
+
[ 'uint32z', 'uint32', 0, 4 ],
|
32
|
+
[ 'byte', 'uint8', 0xFF, 1 ],
|
33
|
+
[ 'sint64', 'int64', 0x7FFFFFFFFFFFFFFF, 8 ],
|
34
|
+
[ 'uint64', 'uint64', 0xFFFFFFFFFFFFFFFF, 8 ],
|
35
|
+
[ 'uint64z', 'uint64', 0, 8 ]
|
36
|
+
]
|
37
|
+
|
38
|
+
def FitDefinitionFieldBase::fit_type_to_bin_data(fit_type)
|
39
|
+
entry = @@TypeDefs.find { |e| e[0] == fit_type }
|
40
|
+
raise "Unknown fit type #{fit_type}" unless entry
|
41
|
+
entry[1]
|
42
|
+
end
|
43
|
+
|
44
|
+
def FitDefinitionFieldBase::undefined_value(fit_type)
|
45
|
+
entry = @@TypeDefs.find { |e| e[0] == fit_type }
|
46
|
+
raise "Unknown fit type #{fit_type}" unless entry
|
47
|
+
entry[2]
|
48
|
+
end
|
49
|
+
|
50
|
+
def set_type(fit_type)
|
51
|
+
idx = @@TypeDefs.index { |x| x[0] == fit_type }
|
52
|
+
raise "Unknown type #{fit_type}" unless idx
|
53
|
+
self.base_type_number = idx
|
54
|
+
self.byte_count = @@TypeDefs[idx][3]
|
55
|
+
end
|
56
|
+
|
57
|
+
def type(fit_type = false)
|
58
|
+
@@TypeDefs[checked_base_type_number][fit_type ? 0 : 1]
|
59
|
+
end
|
60
|
+
|
61
|
+
def is_array?
|
62
|
+
if total_bytes > base_type_bytes
|
63
|
+
if total_bytes % base_type_bytes != 0
|
64
|
+
Log.error "Total bytes (#{total_bytes}) must be multiple of " +
|
65
|
+
"base type bytes (#{base_type_bytes}) of type " +
|
66
|
+
"#{base_type_number.snapshot} in Global FIT " +
|
67
|
+
"Message #{name}."
|
68
|
+
end
|
69
|
+
return true
|
70
|
+
end
|
71
|
+
false
|
72
|
+
end
|
73
|
+
|
74
|
+
def total_bytes
|
75
|
+
self.byte_count.snapshot
|
76
|
+
end
|
77
|
+
|
78
|
+
def base_type_bytes
|
79
|
+
@@TypeDefs[checked_base_type_number][3]
|
80
|
+
end
|
81
|
+
|
82
|
+
def undefined_value
|
83
|
+
@@TypeDefs[checked_base_type_number][2]
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def checked_base_type_number
|
89
|
+
if @@TypeDefs.length <= base_type_number.snapshot
|
90
|
+
Log.error "Unknown FIT Base type #{base_type_number.snapshot} in " +
|
91
|
+
"Global FIT Message #{name}"
|
92
|
+
return 0
|
93
|
+
end
|
94
|
+
base_type_number.snapshot
|
95
|
+
end
|
96
|
+
|
97
|
+
def check_fit_base_type
|
98
|
+
if @@TypeDefs.length <= base_type_number.snapshot
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
# encoding: UTF-8
|
3
|
+
#
|
4
|
+
# = FitDeveloperDataFieldDefinition.rb -- Fit4Ruby - FIT file processing library for Ruby
|
5
|
+
#
|
6
|
+
# Copyright (c) 2017 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/FitDefinitionFieldBase'
|
15
|
+
|
16
|
+
module Fit4Ruby
|
17
|
+
|
18
|
+
class FitDeveloperDataFieldDefinition < BinData::Record
|
19
|
+
|
20
|
+
include FitDefinitionFieldBase
|
21
|
+
|
22
|
+
uint8 :field_number
|
23
|
+
uint8 :size_in_bytes
|
24
|
+
uint8 :developer_data_index
|
25
|
+
|
26
|
+
def name
|
27
|
+
"developer_field_#{developer_data_index.snapshot}_" +
|
28
|
+
"#{field_number.snapshot}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def bindata_type
|
32
|
+
fit_definition = parent.parent
|
33
|
+
if (entry = @@TypeDefs.find { |e| e[3] == size_in_bytes.snapshot })
|
34
|
+
entry[1]
|
35
|
+
else
|
36
|
+
'uint8'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -53,8 +53,8 @@ module Fit4Ruby
|
|
53
53
|
obj = entity.new_fit_data_record(@name)
|
54
54
|
|
55
55
|
# It's important to ensure that alternative fields processed after the
|
56
|
-
# regular fields so that the decision field
|
57
|
-
sorted_fields = @definition.
|
56
|
+
# regular fields so that the decision field has already been already set.
|
57
|
+
sorted_fields = @definition.data_fields.sort do |f1, f2|
|
58
58
|
f1alt = is_alt_field?(f1)
|
59
59
|
f2alt = is_alt_field?(f2)
|
60
60
|
f1alt == f2alt ?
|
@@ -89,6 +89,16 @@ module Fit4Ruby
|
|
89
89
|
(field_def ? field_def : field).to_s(value))
|
90
90
|
end
|
91
91
|
end
|
92
|
+
#@definition.developer_fields.each do |field|
|
93
|
+
# $stderr.puts " ++ New Developer Field " +
|
94
|
+
# "#{field.field_number.snapshot} " +
|
95
|
+
# "Bytes: #{field.size_in_bytes.snapshot} " +
|
96
|
+
# "Developer ID: #{field.developer_data_index.snapshot}"
|
97
|
+
#end
|
98
|
+
|
99
|
+
#if @name == 'field_description'
|
100
|
+
# obj.create_global_definition
|
101
|
+
#end
|
92
102
|
end
|
93
103
|
|
94
104
|
private
|
@@ -131,7 +141,7 @@ module Fit4Ruby
|
|
131
141
|
|
132
142
|
def produce(definition)
|
133
143
|
fields = []
|
134
|
-
definition.
|
144
|
+
definition.data_fields.each do |field|
|
135
145
|
field_def = [ field.type, field.name ]
|
136
146
|
if field.type == 'string'
|
137
147
|
# Strings need special handling. We need to also include the length
|
@@ -146,6 +156,11 @@ module Fit4Ruby
|
|
146
156
|
fields << field_def
|
147
157
|
end
|
148
158
|
|
159
|
+
definition.developer_fields.each do |field|
|
160
|
+
field_def = [ field.bindata_type, field.name ]
|
161
|
+
fields << field_def
|
162
|
+
end
|
163
|
+
|
149
164
|
BinData::Struct.new(:endian => definition.endian, :fields => fields)
|
150
165
|
end
|
151
166
|
|
data/lib/fit4ruby/FitRecord.rb
CHANGED
@@ -36,40 +36,39 @@ module Fit4Ruby
|
|
36
36
|
def read(io, entity, filter, record_counters)
|
37
37
|
header = FitRecordHeader.read(io)
|
38
38
|
|
39
|
-
if
|
40
|
-
# process
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
39
|
+
if header.compressed?
|
40
|
+
# process compressed timestamp header
|
41
|
+
time_offset = header.time_offset.snapshot
|
42
|
+
end
|
43
|
+
|
44
|
+
local_message_type = header.local_message_type.snapshot
|
45
|
+
if header.normal? && header.message_type.snapshot == 1
|
46
|
+
# process definition message
|
47
|
+
definition = FitDefinition.read(io, entity,
|
48
|
+
header.developer_data_flag.snapshot)
|
49
|
+
@definitions[local_message_type] = FitMessageRecord.new(definition)
|
50
|
+
else
|
51
|
+
# process data message
|
52
|
+
definition = @definitions[local_message_type]
|
53
|
+
unless definition
|
54
|
+
Log.fatal "Undefined local message type: #{local_message_type}"
|
55
|
+
end
|
56
|
+
if filter
|
57
|
+
@number = @definitions[local_message_type].global_message_number
|
58
|
+
index = (record_counters[@number] += 1)
|
55
59
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
60
|
+
# Check if we have a filter defined to collect raw dumps of the
|
61
|
+
# data in the message records. The dump is collected in @fields
|
62
|
+
# for later output.
|
63
|
+
if (filter.record_numbers.nil? ||
|
64
|
+
filter.record_numbers.include?(@number)) &&
|
65
|
+
(filter.record_indexes.nil? ||
|
66
|
+
filter.record_indexes.include?(index))
|
67
|
+
@name = definition.name
|
68
|
+
@fields = []
|
66
69
|
end
|
67
|
-
definition.read(io, entity, filter, @fields)
|
68
70
|
end
|
69
|
-
|
70
|
-
# process compressed timestamp header
|
71
|
-
time_offset = header.time_offset
|
72
|
-
Log.fatal "Support for compressed headers not yet implemented"
|
71
|
+
definition.read(io, entity, filter, @fields)
|
73
72
|
end
|
74
73
|
|
75
74
|
self
|
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# = FitRecordHeader.rb -- Fit4Ruby - FIT file processing library for Ruby
|
5
5
|
#
|
6
|
-
# Copyright (c) 2014 by Chris Schlaeger <cs@taskjuggler.org>
|
6
|
+
# Copyright (c) 2014, 2017 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
|
@@ -17,13 +17,13 @@ module Fit4Ruby
|
|
17
17
|
bit1 :normal
|
18
18
|
|
19
19
|
bit1 :message_type, :onlyif => :normal?
|
20
|
-
bit2 :reserved, :onlyif => :normal?
|
21
20
|
|
21
|
+
bit1 :developer_data_flag, :onlyif => :normal?
|
22
|
+
bit1 :reserved, :onlyif => :normal?
|
22
23
|
choice :local_message_type, :selection => :normal do
|
23
24
|
bit4 0
|
24
25
|
bit2 1
|
25
26
|
end
|
26
|
-
|
27
27
|
bit5 :time_offset, :onlyif => :compressed?
|
28
28
|
|
29
29
|
def normal?
|
@@ -283,6 +283,25 @@ module Fit4Ruby
|
|
283
283
|
entry 2, 'paused'
|
284
284
|
entry 3, 'unknown'
|
285
285
|
|
286
|
+
dict 'fit_base_type'
|
287
|
+
entry 0, 'enum'
|
288
|
+
entry 1, 'sint8'
|
289
|
+
entry 2, 'uint8'
|
290
|
+
entry 7, 'string'
|
291
|
+
entry 10, 'uint8z'
|
292
|
+
entry 13, 'byte'
|
293
|
+
entry 131, 'sint16'
|
294
|
+
entry 132, 'uint16'
|
295
|
+
entry 133, 'sint32'
|
296
|
+
entry 134, 'uint32'
|
297
|
+
entry 136, 'float32'
|
298
|
+
entry 137, 'float64'
|
299
|
+
entry 139, 'uint16z'
|
300
|
+
entry 140, 'uint32z'
|
301
|
+
entry 142, 'sint64'
|
302
|
+
entry 143, 'uint64'
|
303
|
+
entry 144, 'uint64z'
|
304
|
+
|
286
305
|
dict 'garmin_product'
|
287
306
|
entry 8, 'hrm_run_single_byte_product_id'
|
288
307
|
entry 1551, 'fenix'
|
@@ -86,7 +86,7 @@ module Fit4Ruby
|
|
86
86
|
end
|
87
87
|
|
88
88
|
def fit_to_native(value)
|
89
|
-
return nil if value ==
|
89
|
+
return nil if value == FitDefinitionFieldBase.undefined_value(@type)
|
90
90
|
|
91
91
|
if @opts.include?(:dict) && (dict = GlobalFitDictionaries[@opts[:dict]])
|
92
92
|
return dict.name(value) || "Undocumented value #{value}"
|
@@ -106,12 +106,12 @@ module Fit4Ruby
|
|
106
106
|
end
|
107
107
|
|
108
108
|
def native_to_fit(value)
|
109
|
-
return
|
109
|
+
return FitDefinitionFieldBase.undefined_value(@type) if value.nil?
|
110
110
|
|
111
111
|
if @opts.include?(:dict) && (dict = GlobalFitDictionaries[@opts[:dict]])
|
112
112
|
unless (dv = dict.value_by_name(value))
|
113
113
|
Log.error "Unknown value '#{value}' assigned to field #{@name}"
|
114
|
-
return
|
114
|
+
return FitDefinitionFieldBase.undefined_value(@type)
|
115
115
|
else
|
116
116
|
return dv
|
117
117
|
end
|
@@ -63,6 +63,7 @@ module Fit4Ruby
|
|
63
63
|
field 53, 'enum', 'undocumented_field_53'
|
64
64
|
field 54, 'enum', 'undocumented_field_54'
|
65
65
|
field 56, 'enum', 'mounting_side'
|
66
|
+
field 55, 'enum', 'undocumented_field_55'
|
66
67
|
field 58, 'uint16', 'autosync_min_steps'
|
67
68
|
field 59, 'uint16', 'autosync_min_time'
|
68
69
|
field 62, 'enum', 'undocumented_field_62'
|
@@ -98,6 +99,7 @@ module Fit4Ruby
|
|
98
99
|
field 127, 'enum', 'undocumented_field_127'
|
99
100
|
field 128, 'enum', 'undocumented_field_128'
|
100
101
|
field 133, 'enum', 'undocumented_field_133'
|
102
|
+
field 138, 'enum', 'undocumented_field_138'
|
101
103
|
|
102
104
|
message 3, 'user_profile'
|
103
105
|
field 0, 'string', 'friendly_name'
|
@@ -115,7 +117,7 @@ module Fit4Ruby
|
|
115
117
|
field 17, 'enum', 'activity_class'
|
116
118
|
field 18, 'enum', 'position_setting', :dict => 'display_position'
|
117
119
|
field 21, 'enum', 'temperature_setting', :dict => 'display_measure'
|
118
|
-
field 24, 'uint8', '
|
120
|
+
field 24, 'uint8', 'birth_year'
|
119
121
|
field 28, 'uint32', 'wake_time', :type => 'duration'
|
120
122
|
field 29, 'uint32', 'sleep_time', :type => 'duration'
|
121
123
|
field 30, 'enum', 'height_setting', :dict => 'display_measure'
|
@@ -199,6 +201,7 @@ module Fit4Ruby
|
|
199
201
|
field 54, 'uint32', 'undocumented_field_54'
|
200
202
|
field 55, 'enum', 'undocumented_field_55'
|
201
203
|
field 57, 'enum', 'undocumented_field_57'
|
204
|
+
field 60, 'enum', 'undocumented_field_60'
|
202
205
|
field 254, 'uint16', 'message_index'
|
203
206
|
|
204
207
|
message 18, 'session'
|
@@ -302,6 +305,12 @@ module Fit4Ruby
|
|
302
305
|
field 134, 'uint16', 'avg_stride_length', :scale => 10000, :unit => 'm' # guessed
|
303
306
|
field 137, 'uint8', 'g_effect', :scale => 10
|
304
307
|
field 138, 'uint8', 'undocumented_field_138'
|
308
|
+
field 151, 'uint16', 'undocumented_field_151'
|
309
|
+
field 152, 'uint32', 'undocumented_field_152'
|
310
|
+
field 157, 'uint16', 'undocumented_field_157'
|
311
|
+
field 158, 'uint16', 'undocumented_field_158'
|
312
|
+
field 153, 'enum', 'undocumented_field_153'
|
313
|
+
field 154, 'enum', 'undocumented_field_154'
|
305
314
|
field 253, 'uint32', 'timestamp', :type => 'date_time'
|
306
315
|
field 254, 'uint16', 'message_index'
|
307
316
|
|
@@ -390,6 +399,8 @@ module Fit4Ruby
|
|
390
399
|
field 118, 'uint16', 'vertical_ratio', :scale => 100, :unit => '%' # guessed
|
391
400
|
field 119, 'uint16', 'avg_gct_balance', :scale => 100, :unit => '%' # guessed
|
392
401
|
field 120, 'uint16', 'avg_stride_length', :scale => 10000, :unit => 'm' # guessed
|
402
|
+
field 125, 'uint16', 'undocumented_field_125'
|
403
|
+
field 126, 'uint16', 'undocumented_field_126'
|
393
404
|
field 253, 'uint32', 'timestamp', :type => 'date_time'
|
394
405
|
field 254, 'uint16', 'message_index'
|
395
406
|
|
@@ -475,8 +486,8 @@ module Fit4Ruby
|
|
475
486
|
# Possibly which device is used as metering source.
|
476
487
|
# Not documented in FIT SDK, so the field names are all guesses right now.
|
477
488
|
message 22, 'data_sources'
|
478
|
-
field 0, 'uint8', '
|
479
|
-
field 1, 'uint8', '
|
489
|
+
field 0, 'uint8', 'speed'
|
490
|
+
field 1, 'uint8', 'distance'
|
480
491
|
field 2, 'uint8', 'cadence'
|
481
492
|
field 3, 'uint8', 'elevation'
|
482
493
|
field 4, 'uint8', 'heart_rate'
|
@@ -515,6 +526,7 @@ module Fit4Ruby
|
|
515
526
|
field 25, 'enum', 'source_type', :dict => 'source_type'
|
516
527
|
field 29, 'uint8', 'undocumented_field_29'
|
517
528
|
field 30, 'uint8', 'undocumented_field_30'
|
529
|
+
field 31, 'uint32', 'undocumented_field_31'
|
518
530
|
field 253, 'uint32', 'timestamp', :type => 'date_time'
|
519
531
|
|
520
532
|
# This message was first seen on the Fenix3HR with firmware 3.0.
|
@@ -787,8 +799,32 @@ module Fit4Ruby
|
|
787
799
|
field 56, 'uint16', 'undocumented_field_56'
|
788
800
|
field 57, 'uint16', 'undocumented_field_57'
|
789
801
|
field 58, 'enum', 'undocumented_field_58'
|
802
|
+
field 59, 'uint8', 'undocumented_field_59'
|
790
803
|
field 254, 'uint16', 'message_index'
|
791
804
|
|
805
|
+
message 206, 'field_description'
|
806
|
+
field 0, 'uint8', 'developer_data_index'
|
807
|
+
field 1, 'uint8', 'field_definition_number'
|
808
|
+
field 2, 'uint8', 'fit_base_type_id'
|
809
|
+
field 3, 'string', 'field_name', :array => true
|
810
|
+
field 4, 'uint8', 'array'
|
811
|
+
field 5, 'string', 'components'
|
812
|
+
field 6, 'uint8', 'scale'
|
813
|
+
field 7, 'sint8', 'offset'
|
814
|
+
field 8, 'string', 'units', :array => true
|
815
|
+
field 9, 'string', 'bits'
|
816
|
+
field 10, 'string', 'accumulate'
|
817
|
+
field 13, 'uint16', 'fit_base_unit_id'
|
818
|
+
field 14, 'uint16', 'native_mesg_num'
|
819
|
+
field 15, 'uint8', 'native_field_num'
|
820
|
+
|
821
|
+
message 207, 'developer_data_id'
|
822
|
+
field 0, 'byte', 'developer_id', :array => true
|
823
|
+
field 1, 'byte', 'application_id', :array => true
|
824
|
+
field 2, 'uint16', 'manufacturer_id'
|
825
|
+
field 3, 'uint8', 'developer_data_index'
|
826
|
+
field 4, 'uint32', 'application_version'
|
827
|
+
|
792
828
|
# Not part of the official ANT SDK doc.
|
793
829
|
message 211, 'undocumented_211'
|
794
830
|
field 0, 'uint8', 'undocumented_field_0'
|
@@ -811,6 +847,21 @@ module Fit4Ruby
|
|
811
847
|
field 15, 'uint16', 'undocumented_field_15'
|
812
848
|
field 253, 'uint32', 'timestamp', :type => 'date_time'
|
813
849
|
|
850
|
+
# Not part of the official ANT SDK doc.
|
851
|
+
message 227, 'stress'
|
852
|
+
field 0, 'sint16', 'undocumented_0'
|
853
|
+
field 1, 'uint32', 'undocumented_1'
|
854
|
+
field 2, 'sint8', 'level'
|
855
|
+
|
856
|
+
message 1024, 'undocumented_1024'
|
857
|
+
field 0, 'enum', 'undocumented_field_0'
|
858
|
+
field 2, 'enum', 'undocumented_field_2'
|
859
|
+
field 15, 'uint16', 'undocumented_field_15'
|
860
|
+
field 40, 'enum', 'undocumented_field_40'
|
861
|
+
field 44, 'enum', 'undocumented_field_44'
|
862
|
+
field 247, 'enum', 'undocumented_field_247'
|
863
|
+
field 255, 'enum', 'undocumented_field_255'
|
864
|
+
|
814
865
|
# Not part of the official ANT SDK doc.
|
815
866
|
message 233, 'undocumented_233'
|
816
867
|
field 2, 'byte', 'undocumented_field_2'
|
data/lib/fit4ruby/version.rb
CHANGED
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.6.
|
4
|
+
version: 1.6.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Schlaeger
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-10-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bindata
|
@@ -69,8 +69,8 @@ dependencies:
|
|
69
69
|
description: |
|
70
70
|
This library can read and write FIT files and convert them into a Ruby data
|
71
71
|
structure for easy processing. This library was written for Garmin devices
|
72
|
-
like the FR620, Fenix 3
|
73
|
-
as well but have not been tested.
|
72
|
+
like the FR620, Fenix 3, Fenix 3 HR, Fenix 5 (s and X). Fit files from other
|
73
|
+
devices may work as well but have not been tested.
|
74
74
|
email: chris@linux.com
|
75
75
|
executables: []
|
76
76
|
extensions: []
|
@@ -86,16 +86,20 @@ files:
|
|
86
86
|
- lib/fit4ruby/Activity.rb
|
87
87
|
- lib/fit4ruby/Converters.rb
|
88
88
|
- lib/fit4ruby/DataSources.rb
|
89
|
+
- lib/fit4ruby/DeveloperDataId.rb
|
89
90
|
- lib/fit4ruby/DeviceInfo.rb
|
90
91
|
- lib/fit4ruby/DumpedField.rb
|
91
92
|
- lib/fit4ruby/EPO_Data.rb
|
92
93
|
- lib/fit4ruby/Event.rb
|
94
|
+
- lib/fit4ruby/FieldDescription.rb
|
93
95
|
- lib/fit4ruby/FileCreator.rb
|
94
96
|
- lib/fit4ruby/FileId.rb
|
95
97
|
- lib/fit4ruby/FileNameCoder.rb
|
96
98
|
- lib/fit4ruby/FitDataRecord.rb
|
97
99
|
- lib/fit4ruby/FitDefinition.rb
|
98
100
|
- lib/fit4ruby/FitDefinitionField.rb
|
101
|
+
- lib/fit4ruby/FitDefinitionFieldBase.rb
|
102
|
+
- lib/fit4ruby/FitDeveloperDataFieldDefinition.rb
|
99
103
|
- lib/fit4ruby/FitFile.rb
|
100
104
|
- lib/fit4ruby/FitFileEntity.rb
|
101
105
|
- lib/fit4ruby/FitFilter.rb
|
@@ -155,7 +159,7 @@ rubyforge_project:
|
|
155
159
|
rubygems_version: 2.2.5
|
156
160
|
signing_key:
|
157
161
|
specification_version: 4
|
158
|
-
summary: Library to read GARMIN FIT files.
|
162
|
+
summary: Library to read and write GARMIN FIT files.
|
159
163
|
test_files:
|
160
164
|
- spec/FileNameCoder_spec.rb
|
161
165
|
- spec/FitFile_spec.rb
|