fit4ruby 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6e5c32929d16d07a0fe95c04cd7699e56a8067a4
4
- data.tar.gz: 2315ae4059cf64dc4cd7d0eeb02c6c42ec5cde26
3
+ metadata.gz: 2b2445f82a3a5c423df2e01cf21fdddd2c77322e
4
+ data.tar.gz: 5e51fe986b7b3fa1435a5b6f0dd8b475d6091c47
5
5
  SHA512:
6
- metadata.gz: 66f5efa6875e7a10f79d304730a767c6bf0d330c3c4e0ce6adf297ff3ea8e440281c548f1064f0c42bf27891a001d70caf3a06980b8dfd7a15bc1536f36a78e6
7
- data.tar.gz: 4cb6346d5f3bfee72c1f23f3d7c567612731824842896cf64302549f39e1b7285aca67a3e8298826befb3a7c54af66dd1309f3298cea2c184cbc3af1d9afe9e4
6
+ metadata.gz: f11797d8a21e49791b04b5299d76e5ae7a14f110f38a7841aaffd5fc0b4e5d3471143c75b1b4f8d8f7e03051759e97bf96b8b370bedcfecee168f7240bad2cd0
7
+ data.tar.gz: 695da88503d5a491f5a4b50d6ed0334f18cf53fbdeaeba0a6a6c7f4ed1de501dea491745ffb8e86415fd858691f2cd02612f1abd778765657dbde47632d69fae
data/lib/fit4ruby.rb CHANGED
@@ -18,8 +18,8 @@ module Fit4Ruby
18
18
  FitFile.new.read(file, filter)
19
19
  end
20
20
 
21
- def self.write(file, activity)
22
- FitFile.new.write(file, activity)
21
+ def self.write(file, top_level_record)
22
+ FitFile.new.write(file, top_level_record)
23
23
  end
24
24
 
25
25
  end
@@ -99,7 +99,7 @@ module Fit4Ruby
99
99
  # @return recovery time in seconds.
100
100
  def recovery_time
101
101
  @events.each do |e|
102
- return e.data if e.event == 'recovery_time'
102
+ return e.recovery_time if e.event == 'recovery_time'
103
103
  end
104
104
 
105
105
  nil
@@ -109,7 +109,7 @@ module Fit4Ruby
109
109
  # based on multiple previous activities.
110
110
  def vo2max
111
111
  @events.each do |e|
112
- return e.data if e.event == 'vo2max'
112
+ return e.vo2max if e.event == 'vo2max'
113
113
  end
114
114
 
115
115
  nil
@@ -120,6 +120,11 @@ module Fit4Ruby
120
120
  @sessions[0].sport
121
121
  end
122
122
 
123
+ # Returns the sport subtype of this activity.
124
+ def sub_sport
125
+ @sessions[0].sub_sport
126
+ end
127
+
123
128
  # Write the Activity data to a file.
124
129
  # @param io [IO] File reference
125
130
  # @param id_mapper [FitMessageIdMapper] Maps global FIT record types to
@@ -24,9 +24,12 @@ module Fit4Ruby
24
24
 
25
25
  # Create instance variables that correspond to every field of the
26
26
  # corresponding FIT data record.
27
- @message.fields.each do |field_number, field|
28
- create_instance_variable(field.name)
27
+ @message.fields_by_name.each do |name, field|
28
+ create_instance_variable(name)
29
29
  end
30
+ # Meta fields are additional fields that are not part of the FIT
31
+ # specification but are convenient to have. These are typcially
32
+ # aggregated or converted values of regular fields.
30
33
  @meta_field_units = {}
31
34
  @timestamp = Time.now
32
35
  end
@@ -40,17 +43,18 @@ module Fit4Ruby
40
43
  def set(name, value)
41
44
  ivar_name = '@' + name
42
45
  unless instance_variable_defined?(ivar_name)
43
- Log.warn("Unknown FIT record field '#{ivar_name}'")
46
+ Log.warn("Unknown FIT record field '#{name}' in global message " +
47
+ "#{@message.name} (#{@message.number}).")
44
48
  return
45
49
  end
46
- instance_variable_set('@' + name, value)
50
+ instance_variable_set(ivar_name, value)
47
51
  end
48
52
 
49
53
  def get(name)
50
54
  ivar_name = '@' + name
51
55
  return nil unless instance_variable_defined?(ivar_name)
52
56
 
53
- instance_variable_get('@' + name)
57
+ instance_variable_get(ivar_name)
54
58
  end
55
59
 
56
60
  def get_as(name, to_unit)
@@ -60,7 +64,7 @@ module Fit4Ruby
60
64
  if @meta_field_units.include?(name)
61
65
  unit = @meta_field_units[name]
62
66
  else
63
- field = @message.find_by_name(name)
67
+ field = @message.fields_by_name[name]
64
68
  unless (unit = field.opts[:unit])
65
69
  Log.fatal "Field #{name} has no unit"
66
70
  end
@@ -70,15 +74,15 @@ module Fit4Ruby
70
74
  end
71
75
 
72
76
  def ==(fdr)
73
- @message.fields.each do |field_number, field|
74
- ivar_name = '@' + field.name
77
+ @message.fields_by_name.each do |name, field|
78
+ ivar_name = '@' + name
75
79
  v1 = field.fit_to_native(field.native_to_fit(
76
80
  instance_variable_get(ivar_name)))
77
81
  v2 = field.fit_to_native(field.native_to_fit(
78
82
  fdr.instance_variable_get(ivar_name)))
79
83
 
80
84
  unless v1 == v2
81
- Log.error "#{field.name}: #{v1} != #{v2}"
85
+ Log.error "#{name}: #{v1} != #{v2}"
82
86
  return false
83
87
  end
84
88
  end
@@ -91,16 +95,22 @@ module Fit4Ruby
91
95
  end
92
96
 
93
97
  def write(io, id_mapper)
94
- global_message_number = @message.number
98
+ # Construct a GlobalFitMessage object that matches exactly the provided
99
+ # set of fields. It does not contain any AltField objects.
100
+ fields = {}
101
+ @message.fields_by_name.each_key do |name|
102
+ fields[name] = instance_variable_get('@' + name)
103
+ end
104
+ global_fit_message = @message.construct(fields)
95
105
 
96
106
  # Map the global message number to the current local message number.
97
- unless (local_message_number = id_mapper.get_local(global_message_number))
107
+ unless (local_message_number = id_mapper.get_local(global_fit_message))
98
108
  # If the current dictionary does not contain the global message
99
109
  # number, we need to create a new entry for it. The index in the
100
110
  # dictionary is the local message number.
101
- local_message_number = id_mapper.add_global(global_message_number)
111
+ local_message_number = id_mapper.add_global(global_fit_message)
102
112
  # Write the definition of the global message number to the file.
103
- @message.write(io, local_message_number)
113
+ global_fit_message.write(io, local_message_number)
104
114
  end
105
115
 
106
116
  # Write data record header.
@@ -112,7 +122,7 @@ module Fit4Ruby
112
122
 
113
123
  # Create a BinData::Struct object to store the data record.
114
124
  fields = []
115
- @message.fields.each do |field_number, field|
125
+ global_fit_message.fields_by_number.each do |field_number, field|
116
126
  bin_data_type = FitDefinitionField.fit_type_to_bin_data(field.type)
117
127
  fields << [ bin_data_type, field.name ]
118
128
  end
@@ -120,7 +130,7 @@ module Fit4Ruby
120
130
 
121
131
  # Fill the BinData::Struct object with the values from the corresponding
122
132
  # instance variables.
123
- @message.fields.each do |field_number, field|
133
+ global_fit_message.fields_by_number.each do |field_number, field|
124
134
  iv = "@#{field.name}"
125
135
  if instance_variable_defined?(iv) &&
126
136
  !(iv_value = instance_variable_get(iv)).nil?
@@ -139,7 +149,7 @@ module Fit4Ruby
139
149
 
140
150
  def inspect
141
151
  fields = {}
142
- @message.fields.each do |field_number, field|
152
+ @message.fields_by_name.each do |name, field|
143
153
  ivar_name = '@' + field.name
144
154
  fields[field.name] = instance_variable_get(ivar_name)
145
155
  end
@@ -15,6 +15,13 @@ require 'fit4ruby/FitDefinitionField'
15
15
 
16
16
  module Fit4Ruby
17
17
 
18
+ # The FitDefinition contains the blueprints for FitMessageRecord segments of
19
+ # FIT files. Before a message record can occur in a FIT file, its definition
20
+ # must be included in the FIT file. The definition holds enough information
21
+ # about the message record to define its size. It also contains some basic
22
+ # information how to interpret the data in the record. To fully understand
23
+ # the message record data the full definition in the GlobalFitMessage is
24
+ # required.
18
25
  class FitDefinition < BinData::Record
19
26
 
20
27
  hide :reserved
@@ -33,11 +40,14 @@ module Fit4Ruby
33
40
  end
34
41
 
35
42
  def check
43
+ if architecture.snapshot > 1
44
+ Log.error "Illegal architecture value #{architecture.snapshot}"
45
+ end
36
46
  fields.each { |f| f.check }
37
47
  end
38
48
 
39
49
  def setup(fit_message_definition)
40
- fit_message_definition.fields.each do |number, f|
50
+ fit_message_definition.fields_by_number.each do |number, f|
41
51
  fdf = FitDefinitionField.new
42
52
  fdf.field_definition_number = number
43
53
  fdf.set_type(f.type)
@@ -17,6 +17,11 @@ require 'fit4ruby/GlobalFitMessage'
17
17
 
18
18
  module Fit4Ruby
19
19
 
20
+ # The FitDefinitionField models the part of the FIT file that contains the
21
+ # template definition for a field of a FitMessageRecord. It should match the
22
+ # corresponding definition in GlobalFitMessages. In case we don't have a
23
+ # known entry in GlobalFitMessages we can still read the file since we know
24
+ # the exact size of the binary records.
20
25
  class FitDefinitionField < BinData::Record
21
26
 
22
27
  @@TypeDefs = [
@@ -28,7 +33,7 @@ module Fit4Ruby
28
33
  [ 'uint16', 'uint16', 0xFFFF, 2 ],
29
34
  [ 'sint32', 'int32', 0x7FFFFFFF, 4 ],
30
35
  [ 'uint32', 'uint32', 0xFFFFFFFF, 4 ],
31
- [ 'string', 'stringz', '', 0 ],
36
+ [ 'string', 'string', '', 0 ],
32
37
  [ 'float32', 'float', 0xFFFFFFFF, 4 ],
33
38
  [ 'float63', 'double', 0xFFFFFFFF, 4 ],
34
39
  [ 'uint8z', 'uint8', 0, 1 ],
@@ -62,9 +67,10 @@ module Fit4Ruby
62
67
  @global_message_definition = GlobalFitMessages[@global_message_number]
63
68
  field_number = field_definition_number.snapshot
64
69
  if @global_message_definition &&
65
- (field = @global_message_definition.fields[field_number])
66
- @name = field.name
67
- @type = field.type
70
+ (field = @global_message_definition.fields_by_number[field_number])
71
+ @name = field.respond_to?('name') ? field.name :
72
+ "choice_#{field_number}"
73
+ @type = field.respond_to?('type') ? field.type : nil
68
74
 
69
75
  if @type && (td = @@TypeDefs[base_type_number]) && td[0] != @type
70
76
  Log.warn "#{@global_message_number}:#{@name} must be of type " +
@@ -88,11 +94,19 @@ module Fit4Ruby
88
94
  value = nil if value == undefined_value
89
95
 
90
96
  field_number = field_definition_number.snapshot
91
- if @global_message_definition &&
92
- (field = @global_message_definition.fields[field_number])
93
- field.to_machine(value)
97
+ if value.kind_of?(Array)
98
+ ary = []
99
+ value.each { |v| ary << to_machine(v) }
100
+ ary
94
101
  else
95
- value
102
+ if @global_message_definition &&
103
+ (field = @global_message_definition.
104
+ fields_by_number[field_number]) &&
105
+ field.respond_to?('to_machine')
106
+ field.to_machine(value)
107
+ else
108
+ value
109
+ end
96
110
  end
97
111
  end
98
112
 
@@ -100,12 +114,18 @@ module Fit4Ruby
100
114
  init unless @global_message_number
101
115
  value = nil if value == undefined_value
102
116
 
103
- field_number = field_definition_number.snapshot
104
- if @global_message_definition &&
105
- (field = @global_message_definition.fields[field_number])
106
- field.to_s(value)
117
+ if value.kind_of?(Array)
118
+ s = '[ '
119
+ value.each { |v| s << to_s(v) + ' ' }
120
+ s + ']'
107
121
  else
108
- "[#{value.to_s}]"
122
+ field_number = field_definition_number.snapshot
123
+ if @global_message_definition &&
124
+ (field = @global_message_definition.fields_by_number[field_number])
125
+ field.to_s(value)
126
+ else
127
+ "[#{value.to_s}]"
128
+ end
109
129
  end
110
130
  end
111
131
 
@@ -117,19 +137,41 @@ module Fit4Ruby
117
137
  end
118
138
 
119
139
  def type(fit_type = false)
120
- if @@TypeDefs.length <= base_type_number.snapshot
121
- Log.fatal "Unknown FIT Base type #{base_type_number.snapshot}"
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
122
151
  end
152
+ false
153
+ end
123
154
 
124
- @@TypeDefs[base_type_number.snapshot][fit_type ? 0 : 1]
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]
125
162
  end
126
163
 
127
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
128
172
  if @@TypeDefs.length <= base_type_number.snapshot
129
173
  Log.fatal "Unknown FIT Base type #{base_type_number.snapshot}"
130
174
  end
131
-
132
- @@TypeDefs[base_type_number.snapshot][2]
133
175
  end
134
176
 
135
177
  end
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # = FitFile.rb -- Fit4Ruby - FIT file processing library for Ruby
5
5
  #
6
- # Copyright (c) 2014 by Chris Schlaeger <cs@taskjuggler.org>
6
+ # Copyright (c) 2014, 2015 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
@@ -12,6 +12,7 @@
12
12
 
13
13
  require 'fit4ruby/Log'
14
14
  require 'fit4ruby/FitHeader'
15
+ require 'fit4ruby/FitFileEntity'
15
16
  require 'fit4ruby/FitRecord'
16
17
  require 'fit4ruby/FitFilter'
17
18
  require 'fit4ruby/FitMessageIdMapper'
@@ -39,7 +40,7 @@ module Fit4Ruby
39
40
 
40
41
  check_crc(io, header.end_pos)
41
42
 
42
- activity = Activity.new
43
+ entity = FitFileEntity.new
43
44
  # This Array holds the raw data of the records that may be needed to
44
45
  # dump a human readable form of the FIT file.
45
46
  records = []
@@ -48,7 +49,7 @@ module Fit4Ruby
48
49
  record_counters = Hash.new { 0 }
49
50
  while io.pos < header.end_pos
50
51
  record = FitRecord.new(definitions)
51
- record.read(io, activity, filter, record_counters)
52
+ record.read(io, entity, filter, record_counters)
52
53
  records << record if filter
53
54
  end
54
55
 
@@ -57,11 +58,11 @@ module Fit4Ruby
57
58
  header.dump if filter && filter.record_numbers.nil?
58
59
  dump_records(records) if filter
59
60
 
60
- activity.check
61
- activity
61
+ entity.check
62
+ entity.top_level_record
62
63
  end
63
64
 
64
- def write(file_name, activity)
65
+ def write(file_name, top_level_record)
65
66
  begin
66
67
  io = ::File.open(file_name, 'wb+')
67
68
  rescue StandardError => e
@@ -74,7 +75,7 @@ module Fit4Ruby
74
75
  # Move the pointer behind the header section.
75
76
  io.seek(start_pos)
76
77
  id_mapper = FitMessageIdMapper.new
77
- activity.write(io, id_mapper)
78
+ top_level_record.write(io, id_mapper)
78
79
  end_pos = io.pos
79
80
 
80
81
  crc = write_crc(io, start_pos, end_pos)
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = FitMessageRecord.rb -- Fit4Ruby - FIT file processing library for Ruby
5
+ #
6
+ # Copyright (c) 2015 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/Activity'
14
+
15
+ module Fit4Ruby
16
+
17
+ # The FIT file is a generic container for all kinds of data. This could be
18
+ # activity data, config files, workout definitions, etc. All data is stored
19
+ # in FIT message records. Also the information what kind of FIT file this is
20
+ # is stored in such a record. When we start reading the file, we actually
21
+ # don't know what kind of file it is until we find the right record to tell
22
+ # us. Since we already need to have gathered some information at this point,
23
+ # we use this utility class to store the read data until we know what Ruby
24
+ # objec we need to use to store it for later consumption.
25
+ class FitFileEntity
26
+
27
+ attr_reader :top_level_record
28
+
29
+ # Create a FitFileEntity.
30
+ def initialize
31
+ @top_level_record = nil
32
+ end
33
+
34
+ # Set what kind of FIT file we are dealing with.
35
+ # @return The Ruby object that will hold the content of the FIT file. It's
36
+ # a derivative of FitDataRecord.
37
+ def set_type(type)
38
+ if @top_level_record
39
+ Log.fatal "FIT file type has already been set to " +
40
+ "#{@top_level_record.class}"
41
+ end
42
+ case type
43
+ when 4, 'activity'
44
+ @top_level_record = Activity.new
45
+ @type = 'activity'
46
+ else
47
+ Log.error "Unsupported FIT file type #{type}"
48
+ return nil
49
+ end
50
+
51
+ @top_level_record
52
+ end
53
+
54
+ # Add a new data record to the top-level object.
55
+ def new_fit_data_record(type)
56
+ return nil unless @top_level_record
57
+
58
+ # We already have a record for the top-level type. Just return it.
59
+ return @top_level_record if type == @type
60
+
61
+ # For all other types, we need to create a new record inside the
62
+ # top-level record.
63
+ @top_level_record.new_fit_data_record(type)
64
+ end
65
+
66
+ # Check the consistency of the top-level object.
67
+ def check
68
+ return false unless @top_level_record
69
+ @top_level_record.check
70
+ end
71
+
72
+ # Write the top-level object into a IO stream.
73
+ def write(io, id_mapper)
74
+ return unless @top_level_record
75
+ @top_level_record.write(io, id_mapper)
76
+ end
77
+
78
+ end
79
+
80
+ end