fit4ruby 1.6.1 → 1.6.2
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.
- 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
|