fitreader 0.2.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/Gemfile.lock +43 -0
- data/README.md +27 -38
- data/fitreader.gemspec +1 -3
- data/lib/fitreader.rb +3 -53
- data/lib/fitreader/data_field.rb +38 -0
- data/lib/fitreader/data_record.rb +14 -0
- data/lib/fitreader/definition_record.rb +28 -0
- data/lib/fitreader/field_definition.rb +8 -12
- data/lib/fitreader/file_header.rb +9 -14
- data/lib/fitreader/fit.rb +49 -0
- data/lib/fitreader/fit_object.rb +8 -0
- data/lib/fitreader/message.rb +82 -0
- data/lib/fitreader/record_header.rb +25 -26
- data/lib/fitreader/sdk/{constants.yml → enums.yml} +0 -81
- data/lib/fitreader/sdk/fields.yml +2476 -0
- data/lib/fitreader/sdk/messages.yml +82 -0
- data/lib/fitreader/sdk/sdk.rb +23 -0
- data/lib/fitreader/unpack.rb +41 -0
- data/lib/fitreader/version.rb +1 -1
- metadata +19 -24
- data/lib/fitreader/definition.rb +0 -34
- data/lib/fitreader/degraded_record.rb +0 -37
- data/lib/fitreader/errors.rb +0 -18
- data/lib/fitreader/field_data.rb +0 -58
- data/lib/fitreader/fitfile.rb +0 -63
- data/lib/fitreader/message_type.rb +0 -24
- data/lib/fitreader/record.rb +0 -170
- data/lib/fitreader/sdk/types.yml +0 -2551
- data/lib/fitreader/static.rb +0 -25
- data/schema +0 -37
- data/spec/fitreader_interfaces_spec.rb +0 -57
- data/spec/fitreader_readfile_spec.rb +0 -17
- data/spec/fitreader_static_spec.rb +0 -17
@@ -0,0 +1,82 @@
|
|
1
|
+
0: :file_id
|
2
|
+
1: :capabilities
|
3
|
+
2: :device_settings
|
4
|
+
3: :user_profile
|
5
|
+
4: :hrm_profile
|
6
|
+
5: :sdm_profile
|
7
|
+
6: :bike_profile
|
8
|
+
7: :zones_target
|
9
|
+
8: :hr_zone
|
10
|
+
9: :power_zone
|
11
|
+
10: :met_zone
|
12
|
+
12: :sport
|
13
|
+
15: :goal
|
14
|
+
18: :session
|
15
|
+
19: :lap
|
16
|
+
20: :record
|
17
|
+
21: :event
|
18
|
+
22: :source
|
19
|
+
23: :device_info
|
20
|
+
26: :workout
|
21
|
+
27: :workout_step
|
22
|
+
28: :schedule
|
23
|
+
29: :location
|
24
|
+
30: :weight_scale
|
25
|
+
31: :course
|
26
|
+
32: :course_point
|
27
|
+
33: :totals
|
28
|
+
34: :activity
|
29
|
+
35: :software
|
30
|
+
37: :file_capabilities
|
31
|
+
38: :mesg_capabilities
|
32
|
+
39: :field_capabilities
|
33
|
+
49: :file_creator
|
34
|
+
51: :blood_pressure
|
35
|
+
53: :speed_zone
|
36
|
+
55: :monitoring
|
37
|
+
72: :training_file
|
38
|
+
78: :hrv
|
39
|
+
80: :ant_rx
|
40
|
+
81: :ant_tx
|
41
|
+
82: :ant_channel_id
|
42
|
+
101: :length
|
43
|
+
103: :monitoring_info
|
44
|
+
104: :battery_info
|
45
|
+
105: :pad
|
46
|
+
106: :slave_device
|
47
|
+
127: :connectivity
|
48
|
+
128: :weather_conditions
|
49
|
+
129: :weather_alert
|
50
|
+
131: :cadence_zone
|
51
|
+
132: :hr
|
52
|
+
142: :segment_lap
|
53
|
+
145: :memo_glob
|
54
|
+
147: :sensor_info
|
55
|
+
148: :segment_id
|
56
|
+
149: :segment_leaderboard_entry
|
57
|
+
150: :segment_point
|
58
|
+
151: :segment_file
|
59
|
+
159: :watchface_settings
|
60
|
+
160: :gps_metadata
|
61
|
+
161: :camera_event
|
62
|
+
162: :timestamp_correlation
|
63
|
+
164: :gyroscope_data
|
64
|
+
165: :accelerometer_data
|
65
|
+
167: :three_d_sensor_calibration
|
66
|
+
169: :video_frame
|
67
|
+
174: :obdii_data
|
68
|
+
177: :nmea_sentence
|
69
|
+
178: :aviation_attitude
|
70
|
+
184: :video
|
71
|
+
185: :video_title
|
72
|
+
186: :video_description
|
73
|
+
187: :video_clip
|
74
|
+
188: :ohr_settings
|
75
|
+
200: :exd_screen_configuration
|
76
|
+
201: :exd_data_field_configuration
|
77
|
+
202: :exd_data_concept_configuration
|
78
|
+
206: :field_description
|
79
|
+
207: :developer_data_id
|
80
|
+
208: :magnetometer_data
|
81
|
+
0xFF00: :mfg_range_min
|
82
|
+
0xFFFE: :mfg_range_max
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
class Sdk
|
4
|
+
@enums ||= YAML.load_file(File.join(File.dirname(__FILE__), 'enums.yml'))
|
5
|
+
@fields ||= YAML.load_file(File.join(File.dirname(__FILE__), 'fields.yml'))
|
6
|
+
@messages ||= YAML.load_file(File.join(File.dirname(__FILE__), 'messages.yml'))
|
7
|
+
|
8
|
+
def self.enum(name)
|
9
|
+
@enums[name]
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.field(msg, field)
|
13
|
+
@fields[msg][field]
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.fields(msg)
|
17
|
+
@fields[msg]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.message(num)
|
21
|
+
@messages[num]
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Unpack
|
2
|
+
def readbytes(io, char, len)
|
3
|
+
io.read(len).unpack(char).first
|
4
|
+
end
|
5
|
+
|
6
|
+
def read_multiple(io, char, len, size)
|
7
|
+
if char == 'Z*'
|
8
|
+
readbytes(io, char, len)
|
9
|
+
else
|
10
|
+
multiples = len / size
|
11
|
+
res = io.read(len).unpack(char * multiples)
|
12
|
+
if res.length == 1
|
13
|
+
res.first
|
14
|
+
else
|
15
|
+
res
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def read_bit(byte, bit)
|
21
|
+
(byte & MASKS[bit]) >> bit
|
22
|
+
end
|
23
|
+
|
24
|
+
def read_bits(byte, range)
|
25
|
+
mask = range.first
|
26
|
+
.downto(range.last)
|
27
|
+
.inject(0) { |sum, i| sum + MASKS[i] }
|
28
|
+
(byte & mask) >> range.last
|
29
|
+
end
|
30
|
+
|
31
|
+
MASKS = {
|
32
|
+
7 => 0b10000000,
|
33
|
+
6 => 0b01000000,
|
34
|
+
5 => 0b00100000,
|
35
|
+
4 => 0b00010000,
|
36
|
+
3 => 0b00001000,
|
37
|
+
2 => 0b00000100,
|
38
|
+
1 => 0b00000010,
|
39
|
+
0 => 0b00000001
|
40
|
+
}.freeze
|
41
|
+
end
|
data/lib/fitreader/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fitreader
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Brodie
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-06-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -56,16 +56,16 @@ dependencies:
|
|
56
56
|
name: pry
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: 0.10.4
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
68
|
+
version: 0.10.4
|
69
69
|
description:
|
70
70
|
email:
|
71
71
|
- richard@samsari.org
|
@@ -76,6 +76,7 @@ files:
|
|
76
76
|
- ".gitignore"
|
77
77
|
- ".travis.yml"
|
78
78
|
- Gemfile
|
79
|
+
- Gemfile.lock
|
79
80
|
- LICENSE
|
80
81
|
- LICENSE.txt
|
81
82
|
- README.md
|
@@ -84,26 +85,23 @@ files:
|
|
84
85
|
- bin/setup
|
85
86
|
- fitreader.gemspec
|
86
87
|
- lib/fitreader.rb
|
87
|
-
- lib/fitreader/
|
88
|
-
- lib/fitreader/
|
89
|
-
- lib/fitreader/
|
90
|
-
- lib/fitreader/field_data.rb
|
88
|
+
- lib/fitreader/data_field.rb
|
89
|
+
- lib/fitreader/data_record.rb
|
90
|
+
- lib/fitreader/definition_record.rb
|
91
91
|
- lib/fitreader/field_definition.rb
|
92
92
|
- lib/fitreader/file_header.rb
|
93
|
-
- lib/fitreader/
|
94
|
-
- lib/fitreader/
|
95
|
-
- lib/fitreader/
|
93
|
+
- lib/fitreader/fit.rb
|
94
|
+
- lib/fitreader/fit_object.rb
|
95
|
+
- lib/fitreader/message.rb
|
96
96
|
- lib/fitreader/record_header.rb
|
97
|
-
- lib/fitreader/sdk/
|
98
|
-
- lib/fitreader/sdk/
|
99
|
-
- lib/fitreader/
|
97
|
+
- lib/fitreader/sdk/enums.yml
|
98
|
+
- lib/fitreader/sdk/fields.yml
|
99
|
+
- lib/fitreader/sdk/messages.yml
|
100
|
+
- lib/fitreader/sdk/sdk.rb
|
101
|
+
- lib/fitreader/unpack.rb
|
100
102
|
- lib/fitreader/version.rb
|
101
|
-
- schema
|
102
103
|
- spec/1471568492.fit
|
103
104
|
- spec/2016-04-09-13-19-18.fit
|
104
|
-
- spec/fitreader_interfaces_spec.rb
|
105
|
-
- spec/fitreader_readfile_spec.rb
|
106
|
-
- spec/fitreader_static_spec.rb
|
107
105
|
- test/fitreader_test.rb
|
108
106
|
- test/test_helper.rb
|
109
107
|
homepage: https://github.com/samsari/fitreader
|
@@ -126,15 +124,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
126
124
|
version: '0'
|
127
125
|
requirements: []
|
128
126
|
rubyforge_project:
|
129
|
-
rubygems_version: 2.6.
|
127
|
+
rubygems_version: 2.6.11
|
130
128
|
signing_key:
|
131
129
|
specification_version: 4
|
132
130
|
summary: Library for reading FIT files generated by Garmin devices.
|
133
131
|
test_files:
|
134
132
|
- spec/1471568492.fit
|
135
133
|
- spec/2016-04-09-13-19-18.fit
|
136
|
-
- spec/fitreader_interfaces_spec.rb
|
137
|
-
- spec/fitreader_readfile_spec.rb
|
138
|
-
- spec/fitreader_static_spec.rb
|
139
134
|
- test/fitreader_test.rb
|
140
135
|
- test/test_helper.rb
|
data/lib/fitreader/definition.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
require 'fitreader/field_definition'
|
2
|
-
|
3
|
-
module Fitreader
|
4
|
-
class Definition
|
5
|
-
attr_accessor :name, :global_num, :local_num, :fit_msg, :field_definitions, :num_fields
|
6
|
-
def initialize(msg_num, bytes)
|
7
|
-
@local_num = msg_num
|
8
|
-
@architecture = bytes[1].unpack('C').first
|
9
|
-
@global_num = bytes[2..3].unpack('v').first
|
10
|
-
@fit_msg = Static.message[@global_num]
|
11
|
-
@name = Static.enums[:enum_mesg_num][@global_num]
|
12
|
-
@num_fields = bytes[4].unpack('C').first
|
13
|
-
end
|
14
|
-
|
15
|
-
def field(id)
|
16
|
-
@field_definitions.find { |x| x.def_num == id }
|
17
|
-
end
|
18
|
-
|
19
|
-
def add_fields(bytes)
|
20
|
-
bytes.chars.each_slice(3) do |x|
|
21
|
-
fd = FieldDefinition.new(@global_num, x)
|
22
|
-
(@field_definitions ||= []).push(fd)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def content_length
|
27
|
-
@field_definitions.inject(0){|sum,x| sum + x.size}
|
28
|
-
end
|
29
|
-
|
30
|
-
def message_type
|
31
|
-
Static.message[@global_num]
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
require 'fitreader/field_data'
|
2
|
-
require 'fitreader/errors'
|
3
|
-
|
4
|
-
module Fitreader
|
5
|
-
class DegradedRecord
|
6
|
-
attr_reader :fields
|
7
|
-
def initialize(definition, bytes)
|
8
|
-
@fields = {}
|
9
|
-
|
10
|
-
start = 0
|
11
|
-
definition.field_definitions.each do |f|
|
12
|
-
raw = bytes[start...start+=f.size]
|
13
|
-
b = Static.base[f.base_num]
|
14
|
-
data = unpack_data(f, b, raw)
|
15
|
-
@fields[f.def_num] = data
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def unpack_data(f, b, raw)
|
20
|
-
if !b[:unpack_type].nil?
|
21
|
-
if b[:size] != f.size && f.base_num != 7
|
22
|
-
data = []
|
23
|
-
s = 0
|
24
|
-
(f.size/b[:size]).times do |_|
|
25
|
-
data.push(raw[s..s+=b[:size]].unpack(b[:unpack_type]).first)
|
26
|
-
end
|
27
|
-
else
|
28
|
-
data = raw.unpack(b[:unpack_type]).first
|
29
|
-
end
|
30
|
-
elsif f.base_num.zero?
|
31
|
-
data = raw.unpack('C').first
|
32
|
-
else
|
33
|
-
data = raw.split(/\0/)[0]
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
data/lib/fitreader/errors.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
module Fitreader
|
2
|
-
class UnknownFieldTypeError < RuntimeError
|
3
|
-
attr :field, :definition, :data, :reason
|
4
|
-
def initialize(definition, field, data, reason)
|
5
|
-
@field = field
|
6
|
-
@definition = definition
|
7
|
-
@data = data
|
8
|
-
@reason = reason
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
class UnknownMessageTypeError < RuntimeError
|
13
|
-
# attr :definition
|
14
|
-
# def initialize(definition)
|
15
|
-
# @definition = definition
|
16
|
-
# end
|
17
|
-
end
|
18
|
-
end
|
data/lib/fitreader/field_data.rb
DELETED
@@ -1,58 +0,0 @@
|
|
1
|
-
module Fitreader
|
2
|
-
class FieldData
|
3
|
-
attr_accessor :id, :raw_value, :value, :name, :valid
|
4
|
-
|
5
|
-
def initialize(id, raw_value, type)
|
6
|
-
@id = id
|
7
|
-
@raw_value = raw_value
|
8
|
-
unless type.nil?
|
9
|
-
process_type(raw_value, type)
|
10
|
-
else
|
11
|
-
puts "nil type #{id} - #{raw_value}"
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
def process_type(data, type)
|
17
|
-
@name = type[:name]
|
18
|
-
if type[:type][0..3].to_sym == :enum
|
19
|
-
data = Static.enums[type[:type]][data]
|
20
|
-
elsif type[:type] == :date_time
|
21
|
-
data = process_datetime(data)
|
22
|
-
elsif type[:type] == :local_date_time
|
23
|
-
data = process_local_datetime(data)
|
24
|
-
elsif type[:type] == :coordinates
|
25
|
-
data = process_coord(data)
|
26
|
-
end
|
27
|
-
|
28
|
-
if type[:scale] != 0
|
29
|
-
if data.class == Array
|
30
|
-
data = data.collect{|x| (x*1.0)/type[:scale]}
|
31
|
-
else
|
32
|
-
data = (data*1.0)/type[:scale]
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
if type[:offset] != 0
|
37
|
-
if data.class == Array
|
38
|
-
data = data.collect{|x| x - type[:offset]}
|
39
|
-
else
|
40
|
-
data = data - type[:offset]
|
41
|
-
end
|
42
|
-
end
|
43
|
-
@value = data
|
44
|
-
end
|
45
|
-
|
46
|
-
def process_local_datetime(data)
|
47
|
-
t = Time.new(1989, 12, 31, 0, 0, 0, '+02:00').utc.to_i
|
48
|
-
Time.at(data+t)
|
49
|
-
end
|
50
|
-
def process_datetime(data)
|
51
|
-
t = Time.new(1989, 12, 31, 0, 0, 0, '+00:00').utc.to_i
|
52
|
-
Time.at(data+t).utc
|
53
|
-
end
|
54
|
-
def process_coord(data)
|
55
|
-
data * ( 180.0 / 2**31 )
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
data/lib/fitreader/fitfile.rb
DELETED
@@ -1,63 +0,0 @@
|
|
1
|
-
require 'fitreader/file_header'
|
2
|
-
require 'fitreader/record_header'
|
3
|
-
require 'fitreader/definition'
|
4
|
-
require 'fitreader/record'
|
5
|
-
require 'fitreader/degraded_record'
|
6
|
-
require 'fitreader/message_type'
|
7
|
-
require 'fitreader/static'
|
8
|
-
|
9
|
-
module Fitreader
|
10
|
-
class FitFile
|
11
|
-
attr_reader :header, :messages, :file
|
12
|
-
|
13
|
-
def initialize(path)
|
14
|
-
if path.is_a? String
|
15
|
-
@file = File.open(path, 'rb')
|
16
|
-
elsif path.is_a? File
|
17
|
-
@file = path
|
18
|
-
end
|
19
|
-
@header = FileHeader.new(@file.read(14))
|
20
|
-
if valid?
|
21
|
-
@defs = {}
|
22
|
-
@messages = {}
|
23
|
-
process_next_record while @file.pos < @header.num_records
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def valid?
|
30
|
-
@header.valid_file
|
31
|
-
end
|
32
|
-
|
33
|
-
def process_next_record
|
34
|
-
h = RecordHeader.new(@file.read(1).unpack('C').first)
|
35
|
-
dr = @defs[h.local_num]
|
36
|
-
if h.header_type == :definition
|
37
|
-
dr = Definition.new(h.local_num, @file.read(5))
|
38
|
-
dr.add_fields(@file.read(dr.num_fields * 3))
|
39
|
-
@defs[h.local_num] = dr
|
40
|
-
m = MessageType.new(dr)
|
41
|
-
(@messages[dr.global_num] ||= []).push m
|
42
|
-
elsif h.header_type == :data
|
43
|
-
unless dr.nil?
|
44
|
-
begin
|
45
|
-
data = @file.read(dr.content_length)
|
46
|
-
datr = Record.new(dr, data)
|
47
|
-
@messages[dr.global_num].last.records.push datr
|
48
|
-
rescue UnknownMessageTypeError
|
49
|
-
degraded = DegradedRecord.new(dr, data)
|
50
|
-
@messages[dr.global_num].last.undefined_records.push degraded
|
51
|
-
end
|
52
|
-
else
|
53
|
-
msg = "no record def found! #{h.local_msg_num}"
|
54
|
-
raise msg
|
55
|
-
end
|
56
|
-
elsif h.header_type == :timestamp
|
57
|
-
raise "timestamp :: msg_num: #{h.local_msg_num}, offset: #{h.timestamp_offset}"
|
58
|
-
else
|
59
|
-
raise 'not a valid record'
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|