fit-parser 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.md +27 -0
  7. data/Rakefile +2 -0
  8. data/fit.gemspec +30 -0
  9. data/lib/fit.rb +23 -0
  10. data/lib/fit/file.rb +29 -0
  11. data/lib/fit/file/data.rb +99 -0
  12. data/lib/fit/file/definition.rb +120 -0
  13. data/lib/fit/file/definitions.rb +705 -0
  14. data/lib/fit/file/header.rb +18 -0
  15. data/lib/fit/file/record.rb +37 -0
  16. data/lib/fit/file/record_header.rb +27 -0
  17. data/lib/fit/file/type.rb +41 -0
  18. data/lib/fit/file/types.rb +954 -0
  19. data/lib/fit/version.rb +3 -0
  20. data/spec/file/data_spec.rb +111 -0
  21. data/spec/file/definition_spec.rb +26 -0
  22. data/spec/file/definitions_spec.rb +81 -0
  23. data/spec/file/header_spec.rb +28 -0
  24. data/spec/file/record_header_spec.rb +20 -0
  25. data/spec/file/record_spec.rb +56 -0
  26. data/spec/file/type_spec.rb +99 -0
  27. data/spec/file/types_spec.rb +100 -0
  28. data/spec/file_spec.rb +21 -0
  29. data/spec/fit_spec.rb +10 -0
  30. data/spec/spec_helper.rb +19 -0
  31. data/spec/support/examples/file/full_file_with_wrong_crc.fit +0 -0
  32. data/spec/support/examples/file/header +0 -0
  33. data/spec/support/examples/file/header_14b.fit +0 -0
  34. data/spec/support/examples/record/data_record_2.fit +0 -0
  35. data/spec/support/examples/record/data_record_2bis.fit +0 -0
  36. data/spec/support/examples/record/definition_record +0 -0
  37. data/spec/support/examples/record/definition_record_2.fit +0 -0
  38. data/spec/support/examples/record/message/data_dynamic_fields.fit +0 -0
  39. data/spec/support/examples/record/message/data_field_array.fit +0 -0
  40. data/spec/support/examples/record/message/data_file_capabilities_activities.fit +0 -0
  41. data/spec/support/examples/record/message/data_file_capabilities_settings.fit +0 -0
  42. data/spec/support/examples/record/message/definition +0 -0
  43. data/spec/support/examples/record/message/definition_dynamic_fields.fit +0 -0
  44. data/spec/support/examples/record/message/definition_field_array.fit +0 -0
  45. data/spec/support/examples/record/message/definition_file_capabilities.fit +0 -0
  46. data/spec/support/examples/record/normal_header +1 -0
  47. metadata +228 -0
@@ -0,0 +1,3 @@
1
+ module Fit
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,111 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fit::File::Data do
4
+ describe ".generate" do
5
+ context 'standard definition' do
6
+ let(:definition) do
7
+ Fit::File::Definition.read example_file('record/message/definition')
8
+ end
9
+
10
+ subject { described_class.generate(definition) }
11
+
12
+ its(:ancestors) { should include(BinData::Record) }
13
+ its("new.record_type") { should eq(:file_id) }
14
+ end
15
+
16
+ context 'definition with multiple time the same field' do
17
+ before :all do
18
+ @fields = Fit::File::Definitions.class_variable_get :@@fields
19
+ @dyn_fields = Fit::File::Definitions.class_variable_get :@@dyn_fields
20
+ # force a fake definition for scaling of arrays
21
+ Fit::File::Definitions.add_field 2, 2, "field_array", :type => 6, :scale => 10, :offset => 0
22
+ end
23
+
24
+ after :all do
25
+ Fit::File::Definitions.class_variable_set :@@fields, @fields
26
+ Fit::File::Definitions.class_variable_set :@@dyn_fields, @dyn_fields
27
+ end
28
+
29
+ before :each do
30
+ def_file = example_file('record/message/definition_field_array.fit')
31
+ data_file = example_file('record/message/data_field_array.fit')
32
+ definition = described_class.generate(Fit::File::Definition.read def_file)
33
+ @result = definition.read( data_file )
34
+ end
35
+
36
+ it "reads the entire record" do
37
+ # read first the record definition
38
+ expect(@result.raw_field_array).to be == [ 123456789, 987654321 ]
39
+ expect(@result.raw_field_4).to be == [ 1, 3 ]
40
+ expect(@result.raw_field_8).to be == 1539
41
+ expect(@result.raw_active_time_zone).to be == 0
42
+ end
43
+
44
+ it "does not apply the scale equal to 1 for integer" do
45
+ expect(@result.raw_active_time_zone).to be == 0
46
+ expect(@result.active_time_zone.to_s).to be_eql '0'
47
+ end
48
+
49
+ it "does not apply the scale equal to 1 for arrays" do
50
+ expect(@result.raw_field_4).to be == [ 1, 3 ]
51
+ expect(@result.field_4.to_s).to be_eql '[1, 3]'
52
+ end
53
+
54
+ it "does apply scale on each element of an array" do
55
+ expect(@result.raw_field_array).to be == [ 123456789, 987654321 ]
56
+ expect(@result.field_array.to_s).to be_eql '[12345678.9, 98765432.1]'
57
+ end
58
+ end
59
+
60
+ context 'definition with dynamic fields' do
61
+ before :each do
62
+ def_file = example_file('record/message/definition_dynamic_fields.fit')
63
+ data_file = example_file('record/message/data_dynamic_fields.fit')
64
+ definition = described_class.generate(Fit::File::Definition.read def_file)
65
+ @result = definition.read( data_file )
66
+ end
67
+
68
+ it 'uses dynamic field value' do
69
+ expect(@result.raw_product).to be == 1499
70
+ expect(@result.product).to be == 'swim'
71
+ end
72
+ end
73
+
74
+ context 'definition with non basic types' do
75
+ before :each do
76
+ def_file = example_file('record/message/definition_dynamic_fields.fit')
77
+ data_file = example_file('record/message/data_dynamic_fields.fit')
78
+ definition = described_class.generate(Fit::File::Definition.read def_file)
79
+ @result = definition.read( data_file )
80
+ end
81
+
82
+ it 'returns the real value' do
83
+ expect(@result.raw_type).to be == 1
84
+ expect(@result.type).to be == 'device'
85
+ end
86
+ end
87
+
88
+ context 'definition with bit field types' do
89
+ before :each do
90
+ def_file = example_file('record/message/definition_file_capabilities.fit')
91
+ @definition = described_class.generate(Fit::File::Definition.read def_file)
92
+ end
93
+
94
+ context 'when only 1 bit set' do
95
+ it 'returns the single value' do
96
+ res = @definition.read( example_file('record/message/data_file_capabilities_activities.fit') )
97
+ expect(res.raw_flags).to eq(2)
98
+ expect(res.flags).to eq('read')
99
+ end
100
+ end
101
+
102
+ context 'when several bits set' do
103
+ it 'returns the compound value' do
104
+ res = @definition.read( example_file('record/message/data_file_capabilities_settings.fit') )
105
+ expect(res.raw_flags).to eq(6)
106
+ expect(res.flags).to eq('read/write')
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fit::File::Definition do
4
+ context "given a sample definition message" do
5
+ describe ".read" do
6
+ subject do
7
+ described_class.read example_file('record/message/definition')
8
+ end
9
+
10
+ its(:architecture) { should == 0 }
11
+ its(:global_message_number) { should == 0 }
12
+ its(:field_count) { should == 6 }
13
+ it { expect(subject.fields.size).to eq(subject.field_count) }
14
+
15
+ its(:record_type) { should == :definition }
16
+ it 'returns the real type for fields' do
17
+ expect(subject.fields[0].real_type).to be == :uint32z
18
+ expect(subject.fields[1].real_type).to be == :date_time
19
+ expect(subject.fields[2].real_type).to be == :manufacturer
20
+ expect(subject.fields[3].real_type).to be == :uint16 # product
21
+ expect(subject.fields[4].real_type).to be == :uint16 # number
22
+ expect(subject.fields[5].real_type).to be == :file
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ # we use undefined numbers for field otherwise we interfere
4
+ # with already defined fields in definitions.rb and test are
5
+ # not really independant of the code
6
+ describe Fit::File::Definitions do
7
+ describe ".add_field" do
8
+ before :all do
9
+ @fields = described_class.class_variable_get :@@fields
10
+ @dyn_fields = described_class.class_variable_get :@@dyn_fields
11
+ end
12
+
13
+ after :all do
14
+ Fit::File::Definitions.class_variable_set(:@@fields, @fields)
15
+ Fit::File::Definitions.class_variable_set(:@@dyn_fields, @dyn_fields)
16
+ end
17
+
18
+ context "without additional options" do
19
+ before :each do
20
+ Fit::File::Definitions.class_variable_set(:@@fields, Hash.new { |h,k| h[k]={} })
21
+ Fit::File::Definitions.class_variable_set(:@@dyn_fields, Hash.new { |h,k| h[k]={} })
22
+ described_class.add_field(999, 999, 'rspec_test')
23
+ end
24
+
25
+ it "adds field data" do
26
+ expect(described_class.get_field(999,999)).to be_a(Hash)
27
+ expect(described_class.get_field(999,999)).to eql({ :name => 'rspec_test'})
28
+ end
29
+
30
+ it 'raised an error for dynamic field data' do
31
+ expect { described_class.add_field(999, 999, 'rspec_test_dyn') }.to raise_error
32
+ end
33
+ end
34
+
35
+ context "with additional options" do
36
+ before :each do
37
+ Fit::File::Definitions.class_variable_set(:@@fields, Hash.new { |h,k| h[k]={} })
38
+ Fit::File::Definitions.class_variable_set(:@@dyn_fields, Hash.new { |h,k| h[k]={} })
39
+
40
+ described_class.add_field(999, 999, 'rspec_test', :scale => 100, :units => 'm')
41
+ described_class.add_field(999, 999, 'rspec_test_dyn', :type => 4, :scale => 10, :offset => 10, :ref_field_name => nil, :ref_field_values => nil)
42
+ end
43
+
44
+ it "adds field data" do
45
+ expect(described_class.get_field(999, 999)).to be_a(Hash)
46
+ expect(described_class.get_field(999, 999)).to eql({ :name => 'rspec_test', :scale => 100, :units => 'm'})
47
+ end
48
+
49
+ it 'adds dynamic field data' do
50
+ expect(described_class.get_dynamic_fields(999, 999)).to be_a(Hash)
51
+ expect(described_class.get_dynamic_fields(999, 999)).to eql({ :rspec_test_dyn => {:type => 4, :scale => 10, :offset => 10, :ref_field_name => nil, :ref_field_values => nil} })
52
+ end
53
+ end
54
+ end
55
+
56
+ describe ".get_field" do
57
+ it "returns nil if no field exists" do
58
+ expect(described_class.get_field(100,100)).to be_nil
59
+ end
60
+ end
61
+
62
+ describe '.get_dynamic_field' do
63
+ it 'returns nil if no dynamic field exists' do
64
+ described_class.add_field(100, 100, 'rspec')
65
+ expect(described_class.get_dynamic_fields(100, 100)).to be_nil
66
+ end
67
+ end
68
+
69
+ describe ".add_name" do
70
+ it "adds a name" do
71
+ described_class.add_name(20, 'record')
72
+ expect(described_class.get_name(20)).to eql('record')
73
+ end
74
+ end
75
+
76
+ describe ".get_name" do
77
+ it "returns nil if no name exists" do
78
+ expect(described_class.get_name(100)).to be_nil
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fit::File::Header do
4
+ context "given a sample FIT header" do
5
+ subject do
6
+ described_class.read example_file('file/header')
7
+ end
8
+
9
+ its(:header_size) { should == 12 }
10
+ its(:protocol_version) { should == 16 }
11
+ its(:profile_version) { should == 64 }
12
+ its(:data_size) { should == 36069 }
13
+ its(:data_type) { should == ".FIT" }
14
+ end
15
+
16
+ context "given a sample header file of 14 bytes length" do
17
+ subject do
18
+ described_class.read example_file('file/header_14b.fit')
19
+ end
20
+
21
+ its(:header_size) { should == 14 }
22
+ its(:protocol_version) { should == 16 }
23
+ its(:profile_version) { should == 411 }
24
+ its(:data_size) { should == 325 }
25
+ its(:data_type) { should == ".FIT" }
26
+ its(:crc) { should == 17101 }
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fit::File::RecordHeader do
4
+ context "given a sample normal header" do
5
+ subject do
6
+ described_class.read example_file('record/normal_header')
7
+ end
8
+
9
+ its(:header_type) { should == 0 }
10
+ its(:message_type) { should == 1 }
11
+ its(:local_message_type) { should == 0 }
12
+
13
+ it { is_expected.to be_normal }
14
+ it { is_expected.not_to be_a_compressed_timestamp }
15
+ end
16
+
17
+ context "given a sample compressed timestamp header" do
18
+ # TODO
19
+ end
20
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fit::File::Record do
4
+ before do
5
+ described_class.clear_definitions!
6
+ described_class.read(example_file('record/definition_record_2.fit'))
7
+ end
8
+
9
+ describe ".read" do
10
+ subject { described_class.read(file) }
11
+
12
+ context "given a sample definition record" do
13
+ let(:file) { example_file('record/definition_record') }
14
+
15
+ it { expect(subject.header).to be_a Fit::File::RecordHeader }
16
+ it { expect(subject.content).to be_a Fit::File::Definition }
17
+
18
+ end
19
+
20
+ context "given a sample data record" do
21
+ let(:file) { nil }
22
+ end
23
+
24
+ context "given a sample data record with a string non null terminated" do
25
+
26
+ context 'string length is equal to field size' do
27
+
28
+ let(:file) { example_file('record/data_record_2.fit') }
29
+
30
+ its(:header) { should be_a(Fit::File::RecordHeader) }
31
+ it { expect(subject.content.raw_version).to be == 250 }
32
+ it { expect(subject.content.raw_part_number).to be == '123-A1234-00' }
33
+ end
34
+
35
+ context 'string length is smaller than field size' do
36
+
37
+ let(:file) { example_file('record/data_record_2bis.fit') }
38
+
39
+ its(:header) { should be_a(Fit::File::RecordHeader) }
40
+ it { expect(subject.content.raw_version).to be == 251 }
41
+ it { expect(subject.content.version).to be == 2.51 }
42
+ it { expect(subject.content.raw_part_number).to be == '123-A1234' }
43
+ it { expect(subject.content.part_number).to be == '123-A1234' }
44
+ end
45
+ end
46
+ end
47
+
48
+ describe ".clear_definitions" do
49
+ it "should clear the definitions class variable" do
50
+ described_class.read example_file('record/definition_record')
51
+ expect(described_class.definitions).to_not be_empty
52
+ described_class.clear_definitions!
53
+ expect(described_class.definitions).to be_empty
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,99 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fit::File::Type do
4
+ before :all do
5
+ @types = Fit::File::Types.class_variable_get :@@types
6
+ Fit::File::Types.add_type(:int_type, :sint8)
7
+ Fit::File::Types.add_type(:int_type_with_val, :uint8, :values => {1 => 'one', 2 => 'two', 3 => 'three'})
8
+ end
9
+
10
+ after :all do
11
+ Fit::File::Types.class_variable_set(:@@types, @types)
12
+ end
13
+
14
+ describe '.get_type' do
15
+ context 'when valid name' do
16
+ it 'returns a type' do
17
+ expect(described_class.get_type(:int_type)).to be_a Fit::File::Type
18
+ end
19
+
20
+ it 'returns always the same instance' do
21
+ expect(described_class.get_type(:int_type)).to equal described_class.get_type(:int_type)
22
+ end
23
+ end
24
+
25
+ context 'when invalid name' do
26
+ it 'returns nil' do
27
+ expect(described_class.get_type(:unknown_type)).to be_nil
28
+ end
29
+ end
30
+ end
31
+
32
+ describe '#value' do
33
+ context 'when type has values' do
34
+
35
+ let(:type) { described_class.get_type(:int_type_with_val) }
36
+
37
+ context 'known value requested' do
38
+ it 'returns the value' do
39
+ expect(type.value(2)).to eql 'two'
40
+ end
41
+ end
42
+
43
+ context 'unknown value requested' do
44
+ it 'returns the input value' do
45
+ expect(type.value(999)).to eql 999
46
+ end
47
+ end
48
+
49
+ context 'when invalid value is requested' do
50
+ it 'returns nil' do
51
+ expect(type.value(255)).to be_nil
52
+ expect(type.value(0xFF)).to be_nil
53
+ end
54
+ end
55
+ end
56
+
57
+ context 'when type has date_time value' do
58
+ let(:type) { described_class.get_type(:date_time) }
59
+ it 'returns the date' do
60
+ expect(type.value(790509304)).to eq('2015-01-18 09:55:04 UTC')
61
+ end
62
+ end
63
+
64
+ context 'when type has message_index value' do
65
+ let(:type) { described_class.get_type(:message_index) }
66
+
67
+ it 'returns the message_index' do
68
+ expect(type.value(10)).to eq(10)
69
+ expect(type.value(32778)).to eq(10)
70
+ expect(type.value(28682)).to eq(10)
71
+ end
72
+
73
+ end
74
+
75
+ context 'when type has file_flags value' do
76
+ let(:type) { described_class.get_type(:file_flags) }
77
+ it 'returns the file_flags' do
78
+ expect(type.value(10)).to eq('read/erase')
79
+ expect(type.value(0x0A)).to eq('read/erase')
80
+ end
81
+ end
82
+
83
+ context 'when type has bool value' do
84
+ let(:type) { described_class.get_type(:bool) }
85
+ it 'returns the boolean value' do
86
+ expect(type.value(0)).to eq(false)
87
+ expect(type.value(1)).to eq(true)
88
+ expect(type.value(255)).to be_nil
89
+ end
90
+ end
91
+
92
+ context 'when type has no value' do
93
+ it 'returns nil' do
94
+ type = described_class.get_type(:int_type)
95
+ expect(type.value(1)).to eql 1
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fit::File::Types do
4
+ before :all do
5
+ @types = described_class.class_variable_get :@@types
6
+ end
7
+
8
+ after :all do
9
+ Fit::File::Types.class_variable_set(:@@types, @types)
10
+ end
11
+
12
+ describe '.add_type' do
13
+ before :each do
14
+ Fit::File::Types.class_variable_set(:@@types, Hash.new { |h,k| h[k]={} })
15
+ end
16
+
17
+ context 'for enum type' do
18
+ it 'add enum data' do
19
+ val = {:values => { 1 => 'val1', 2=> 'val2', 3 => 'val3'}}
20
+ described_class.add_type(:test_enum, :enum, val)
21
+ expect(described_class.get_type_definition(:test_enum)).to eql val.merge({:basic_type => :enum})
22
+ end
23
+ end
24
+ end
25
+
26
+ describe '.get_type_definition' do
27
+ it 'returns nil when type does not exist' do
28
+ expect(described_class.get_type_definition(:rspec_unknown)).to be_nil
29
+ end
30
+ end
31
+
32
+ describe '.date_time_value' do
33
+ context 'wen value below min' do
34
+ it 'returns system time in second' do
35
+ expect(described_class.date_time_value(9999, {268435456 => 'min'}, {:utc => true})).to eql '9999'
36
+ expect(described_class.date_time_value(9999, {268435456 => 'min'}, {:utc => false})).to eql '9999'
37
+ end
38
+ end
39
+
40
+ context 'when value is above min' do
41
+ context 'with UTC mode' do
42
+ it 'returns exact date UTC' do
43
+ expect(described_class.date_time_value(790509304, {268435456 => 'min'}, {:utc => true})).to eql '2015-01-18 09:55:04 UTC'
44
+ end
45
+ end
46
+
47
+ context 'with local mode' do
48
+ it 'returns exact date in locale time zone' do
49
+ # TODO: manage answer based on current system local
50
+ expect(described_class.date_time_value(790509304, {268435456 => 'min'}, {:utc => false})).not_to match(/UTC$/)
51
+ end
52
+ end
53
+
54
+ end
55
+ end
56
+
57
+ describe '.message_index_value' do
58
+ let(:values) { {32768 => 'selected', 28672 => 'reserved', 4095 => 'mask' } }
59
+
60
+ context 'when value is not reserved or selected' do
61
+ it 'returns the message index' do
62
+ expect(described_class.message_index_value(10, values)).to eq(10)
63
+ end
64
+ end
65
+
66
+ context 'when value is reserved' do
67
+ it 'returns real message index' do
68
+ expect(described_class.message_index_value(28682, values)).to eq(10)
69
+ end
70
+ end
71
+
72
+ context 'when value is selected' do
73
+ it 'returns real message index' do
74
+ expect(described_class.message_index_value(32778, values)).to eq(10)
75
+ end
76
+ end
77
+
78
+ end
79
+
80
+ describe '.bitfield_value' do
81
+ let(:values) { {0x02 => 'read', 0x04 => 'write', 0x08 => 'erase'} }
82
+
83
+ context 'when value is a single bit' do
84
+ it 'returns the single value' do
85
+ expect(described_class.bitfield_value(2, values)).to eq('read')
86
+ expect(described_class.bitfield_value(4, values)).to eq('write')
87
+ expect(described_class.bitfield_value(8, values)).to eq('erase')
88
+ end
89
+ end
90
+
91
+ context 'when value is several bits' do
92
+ it 'returns the values separated by a slash' do
93
+ expect(described_class.bitfield_value(6, values)).to eq('read/write')
94
+ expect(described_class.bitfield_value(12, values)).to eq('write/erase')
95
+ end
96
+ end
97
+ end
98
+
99
+ end
100
+