fit4ruby 0.0.1 → 0.0.2

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: 144c1ecdd95c3891be340d4cf3d9aa8c772adbf3
4
- data.tar.gz: 0a7597be881101646da02088686489571a586a2f
3
+ metadata.gz: 49de464c7356d1b55144fac48b0f6150371e29a9
4
+ data.tar.gz: 4b5e2057123c3e4a62c6f2335182cfd6cf8aa8ea
5
5
  SHA512:
6
- metadata.gz: 3404459e10d9761a1385fcc4f4f48451a3e7b511889783c0cc700b7b98b06d4d9bbfa3618f0d9345a43f995a64a6cc7f949248df72ad0e095b617a4e92263fe9
7
- data.tar.gz: f716083bb2fcc2eaad00e06b5d5b5e24f3169f9f22d1a21fad0104e7720a96bcf366a1cda5ee60de7b2e7821629b9b73f5cc79bf02ee1f5f7b53881a070051ba
6
+ metadata.gz: b8688224199cac6c1b024711d680bb98b33cb761e46d36af2d83e61aa04701a9f182fc70f5dd442f4a8311c6be43f7a6bf1ccf063034fd31e751191dfff8c2a2
7
+ data.tar.gz: 4c0bdd82555fb57d705d8992c21aa48a01b689d9596b61199894a89604d24dd4e159e55e86fcdd8b1327b4070270b688a78e0628e2caaad929f0308af4f88c4a
data/COPYING CHANGED
@@ -1,12 +1,12 @@
1
- GNU GENERAL PUBLIC LICENSE
2
- Version 2, June 1991
1
+ GNU GENERAL PUBLIC LICENSE
2
+ Version 2, June 1991
3
3
 
4
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.
5
- 675 Mass Ave, Cambridge, MA 02139, USA
4
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6
6
  Everyone is permitted to copy and distribute verbatim copies
7
7
  of this license document, but changing it is not allowed.
8
8
 
9
- Preamble
9
+ Preamble
10
10
 
11
11
  The licenses for most software are designed to take away your
12
12
  freedom to share and change it. By contrast, the GNU General Public
@@ -15,7 +15,7 @@ software--to make sure the software is free for all its users. This
15
15
  General Public License applies to most of the Free Software
16
16
  Foundation's software and to any other program whose authors commit to
17
17
  using it. (Some other Free Software Foundation software is covered by
18
- the GNU Library General Public License instead.) You can apply it to
18
+ the GNU Lesser General Public License instead.) You can apply it to
19
19
  your programs, too.
20
20
 
21
21
  When we speak of free software, we are referring to freedom, not
@@ -55,8 +55,8 @@ patent must be licensed for everyone's free use or not licensed at all.
55
55
 
56
56
  The precise terms and conditions for copying, distribution and
57
57
  modification follow.
58
-
59
- GNU GENERAL PUBLIC LICENSE
58
+
59
+ GNU GENERAL PUBLIC LICENSE
60
60
  TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61
61
 
62
62
  0. This License applies to any program or other work which contains
@@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions:
110
110
  License. (Exception: if the Program itself is interactive but
111
111
  does not normally print such an announcement, your work based on
112
112
  the Program is not required to print an announcement.)
113
-
113
+
114
114
  These requirements apply to the modified work as a whole. If
115
115
  identifiable sections of that work are not derived from the Program,
116
116
  and can be reasonably considered independent and separate works in
@@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent
168
168
  access to copy the source code from the same place counts as
169
169
  distribution of the source code, even though third parties are not
170
170
  compelled to copy the source along with the object code.
171
-
171
+
172
172
  4. You may not copy, modify, sublicense, or distribute the Program
173
173
  except as expressly provided under this License. Any attempt
174
174
  otherwise to copy, modify, sublicense or distribute the Program is
@@ -225,7 +225,7 @@ impose that choice.
225
225
 
226
226
  This section is intended to make thoroughly clear what is believed to
227
227
  be a consequence of the rest of this License.
228
-
228
+
229
229
  8. If the distribution and/or use of the Program is restricted in
230
230
  certain countries either by patents or by copyrighted interfaces, the
231
231
  original copyright holder who places the Program under this License
@@ -255,7 +255,7 @@ make exceptions for this. Our decision will be guided by the two goals
255
255
  of preserving the free status of all derivatives of our free software and
256
256
  of promoting the sharing and reuse of software generally.
257
257
 
258
- NO WARRANTY
258
+ NO WARRANTY
259
259
 
260
260
  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261
261
  FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
@@ -277,4 +277,63 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277
277
  PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278
278
  POSSIBILITY OF SUCH DAMAGES.
279
279
 
280
- END OF TERMS AND CONDITIONS
280
+ END OF TERMS AND CONDITIONS
281
+
282
+ How to Apply These Terms to Your New Programs
283
+
284
+ If you develop a new program, and you want it to be of the greatest
285
+ possible use to the public, the best way to achieve this is to make it
286
+ free software which everyone can redistribute and change under these terms.
287
+
288
+ To do so, attach the following notices to the program. It is safest
289
+ to attach them to the start of each source file to most effectively
290
+ convey the exclusion of warranty; and each file should have at least
291
+ the "copyright" line and a pointer to where the full notice is found.
292
+
293
+ <one line to give the program's name and a brief idea of what it does.>
294
+ Copyright (C) <year> <name of author>
295
+
296
+ This program is free software; you can redistribute it and/or modify
297
+ it under the terms of the GNU General Public License as published by
298
+ the Free Software Foundation; either version 2 of the License, or
299
+ (at your option) any later version.
300
+
301
+ This program is distributed in the hope that it will be useful,
302
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
303
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304
+ GNU General Public License for more details.
305
+
306
+ You should have received a copy of the GNU General Public License along
307
+ with this program; if not, write to the Free Software Foundation, Inc.,
308
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309
+
310
+ Also add information on how to contact you by electronic and paper mail.
311
+
312
+ If the program is interactive, make it output a short notice like this
313
+ when it starts in an interactive mode:
314
+
315
+ Gnomovision version 69, Copyright (C) year name of author
316
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317
+ This is free software, and you are welcome to redistribute it
318
+ under certain conditions; type `show c' for details.
319
+
320
+ The hypothetical commands `show w' and `show c' should show the appropriate
321
+ parts of the General Public License. Of course, the commands you use may
322
+ be called something other than `show w' and `show c'; they could even be
323
+ mouse-clicks or menu items--whatever suits your program.
324
+
325
+ You should also get your employer (if you work as a programmer) or your
326
+ school, if any, to sign a "copyright disclaimer" for the program, if
327
+ necessary. Here is a sample; alter the names:
328
+
329
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
331
+
332
+ <signature of Ty Coon>, 1 April 1989
333
+ Ty Coon, President of Vice
334
+
335
+ This General Public License does not permit incorporating your program into
336
+ proprietary programs. If your program is a subroutine library, you may
337
+ consider it more useful to permit linking proprietary applications with the
338
+ library. If this is what you want to do, use the GNU Lesser General
339
+ Public License instead of this License.
data/Rakefile CHANGED
@@ -4,6 +4,7 @@ $:.unshift File.join(File.dirname(__FILE__))
4
4
  lib = File.expand_path('../lib', __FILE__)
5
5
  $:.unshift lib unless $:.include?(lib)
6
6
 
7
+ require "rspec/core/rake_task"
7
8
  require 'rake/clean'
8
9
 
9
10
  Dir.glob( 'tasks/*.rake').each do |fn|
@@ -14,6 +15,7 @@ Dir.glob( 'tasks/*.rake').each do |fn|
14
15
  end
15
16
  end
16
17
 
17
- task :default => [ :test ]
18
+ task :default => :spec
19
+ task :test => :spec
18
20
 
19
21
  desc 'Run all unit and spec tests'
data/lib/fit4ruby.rb CHANGED
@@ -14,8 +14,8 @@ require 'fit4ruby/FitFile'
14
14
 
15
15
  module Fit4Ruby
16
16
 
17
- def self.read(file, dump = false)
18
- FitFile.new.read(file, dump)
17
+ def self.read(file, filter = nil)
18
+ FitFile.new.read(file, filter)
19
19
  end
20
20
 
21
21
  def self.write(file, activity)
@@ -10,38 +10,50 @@
10
10
  # published by the Free Software Foundation.
11
11
  #
12
12
 
13
+ require 'fit4ruby/FitDataRecord'
14
+ require 'fit4ruby/FileId'
15
+ require 'fit4ruby/FileCreator'
16
+ require 'fit4ruby/DeviceInfo'
17
+ require 'fit4ruby/UserProfile'
13
18
  require 'fit4ruby/Session'
14
19
  require 'fit4ruby/Lap'
15
20
  require 'fit4ruby/Record'
16
- require 'fit4ruby/FitDataRecord'
21
+ require 'fit4ruby/Event'
22
+ require 'fit4ruby/PersonalRecords'
17
23
 
18
24
  module Fit4Ruby
19
25
 
20
26
  class Activity < FitDataRecord
21
27
 
22
- attr_accessor :start_time, :duration, :local_start_time,
23
- :sessions, :laps, :records
28
+ attr_accessor :file_id, :file_creator, :device_infos, :user_profiles,
29
+ :sessions, :laps, :records, :events, :personal_records
24
30
 
25
31
  def initialize
26
32
  super('activity')
27
- rename('timestamp', 'start_time')
28
- rename('local_timestamp', 'local_start_time')
29
- rename('total_timer_time', 'duration')
30
- @start_time = nil
31
- @local_start_time = nil
32
- @duration = nil
33
33
  @num_sessions = 0
34
+
35
+ @file_id = FileId.new
36
+ @file_creator = FileCreator.new
37
+ @device_infos = []
38
+ @user_profiles = []
39
+ @events = []
34
40
  @sessions = []
35
41
  @laps = []
36
42
  @records = []
43
+ @personal_records = []
44
+
45
+ @cur_session_laps = []
46
+ @cur_lap_records = []
47
+
48
+ @lap_counter = 1
37
49
  end
38
50
 
39
51
  def check
40
- unless @start_time && @start_time >= Time.parse('1990-01-01')
41
- Log.error "Activity has no valid start time"
52
+ unless @timestamp && @timestamp >= Time.parse('1990-01-01')
53
+ Log.error "Activity has no valid timestamp"
42
54
  end
43
- unless @duration
44
- Log.error "Activity has no valid duration"
55
+ unless @total_timer_time
56
+ Log.error "Activity has no valid total_timer_time"
45
57
  end
46
58
  unless @num_sessions == @sessions.count
47
59
  Log.error "Activity record requires #{@num_sessions}, but "
@@ -51,66 +63,132 @@ module Fit4Ruby
51
63
  @sessions.each { |s| s.check(self) }
52
64
  end
53
65
 
54
- def set(field, value)
55
- case field
56
- when 'timestamp'
57
- @start_time = value
58
- when 'local_timestamp'
59
- @local_start_time = value
60
- when 'total_timer_time'
61
- @duration = value
62
- when 'num_sessions'
63
- @num_sessions = value
66
+ def total_distance
67
+ d = 0.0
68
+ @sessions.each { |s| d += s.total_distance }
69
+ d
70
+ end
71
+
72
+ def aggregate
73
+ @sessions.each { |s| s.aggregate }
74
+ end
75
+
76
+ def avg_speed
77
+ speed = 0.0
78
+ @sessions.each { |s| speed += s.avg_speed }
79
+ speed / @sessions.length
80
+ end
81
+
82
+ def recovery_time
83
+ @events.each do |e|
84
+ return e.data if e.event == 'recovery_time'
64
85
  end
86
+
87
+ nil
65
88
  end
66
89
 
67
- def method_missing(method_name, *args, &block)
68
- acc_fields = [ :distance ]
69
- avg_fields = [ :avg_speed ]
90
+ def vo2max
91
+ @events.each do |e|
92
+ return e.data if e.event == 'vo2max'
93
+ end
70
94
 
71
- if acc_fields.include?(method_name)
72
- total = 0
73
- @sessions.each do |s|
74
- total += s.send(method_name, *args, &block)
75
- end
76
- return total
77
- elsif avg_fields.include?(method_name)
78
- total = 0
79
- count = 0
80
- return 0 if @sessions.empty?
81
-
82
- @sessions.each do |s|
83
- total += s.send(method_name, *args, &block)
84
- count += 1
85
- end
86
- return total / count
87
- else
88
- Log.fatal "Unknown data field: #{method_name}"
95
+ nil
96
+ end
97
+
98
+ def write(io, id_mapper)
99
+ @file_id.write(io, id_mapper)
100
+ @file_creator.write(io, id_mapper)
101
+
102
+ (@device_infos + @user_profiles + @events + @sessions + @laps +
103
+ @records + @personal_records).sort.each do |s|
104
+ s.write(io, id_mapper)
89
105
  end
106
+ super
90
107
  end
91
108
 
92
- def new_session
93
- @sessions << (session = Session.new)
94
- session
109
+ def new_file_id(field_values = {})
110
+ new_fit_data_record('file_id', field_values)
95
111
  end
96
112
 
97
- def new_lap
98
- @laps << (lap = Lap.new)
99
- lap
113
+ def new_file_creator(field_values = {})
114
+ new_fit_data_record('file_creator', field_values)
100
115
  end
101
116
 
102
- def new_record
103
- @records << (record = Record.new)
104
- record
117
+ def new_device_info(field_values = {})
118
+ new_fit_data_record('device_info', field_values)
119
+ end
120
+
121
+ def new_user_profile(field_values = {})
122
+ new_fit_data_record('user_profile', field_values)
123
+ end
124
+
125
+ def new_event(field_values = {})
126
+ new_fit_data_record('event', field_values)
105
127
  end
106
128
 
107
- def to_s
108
- str = ''
109
- @sessions.each do |s|
110
- str << "\n\n" unless str.empty?
111
- str << s.to_s
129
+ def new_session(field_values = {})
130
+ new_fit_data_record('session', field_values)
131
+ end
132
+
133
+ def new_lap(field_values = {})
134
+ new_fit_data_record('lap', field_values)
135
+ end
136
+
137
+ def new_personal_record(field_values = {})
138
+ new_fit_data_record('personal_record', field_values)
139
+ end
140
+
141
+ def new_record(field_values = {})
142
+ new_fit_data_record('record', field_values)
143
+ end
144
+
145
+ def ==(a)
146
+ super(a) && @file_id == a.file_id &&
147
+ @file_creator == a.file_creator &&
148
+ @device_infos == a.device_infos && @user_profiles == a.user_profiles &&
149
+ @events == a.events &&
150
+ @sessions == a.sessions && personal_records == a.personal_records
151
+ end
152
+
153
+ def new_fit_data_record(record_type, field_values = {})
154
+ case record_type
155
+ when 'file_id'
156
+ @file_id = (record = FileId.new(field_values))
157
+ when 'file_creator'
158
+ @file_creator = (record = FileCreator.new(field_values))
159
+ when 'device_info'
160
+ @device_infos << (record = DeviceInfo.new(field_values))
161
+ when 'user_profile'
162
+ @user_profiles << (record = UserProfile.new(field_values))
163
+ when 'event'
164
+ @events << (record = Event.new(field_values))
165
+ when 'session'
166
+ unless @cur_lap_records.empty?
167
+ # Ensure that all previous records have been assigned to a lap.
168
+ @lap_counter += 1
169
+ @cur_session_laps << (lap = Lap.new(@cur_lap_records, field_values))
170
+ @laps << lap
171
+ @cur_lap_records = []
172
+ end
173
+ @num_sessions += 1
174
+ @sessions << (record = Session.new(@cur_session_laps, @lap_counter,
175
+ field_values))
176
+ @cur_session_laps = []
177
+ when 'lap'
178
+ @lap_counter += 1
179
+ @cur_session_laps << (record = Lap.new(@cur_lap_records, field_values))
180
+ @laps << record
181
+ @cur_lap_records = []
182
+ when 'record'
183
+ @cur_lap_records << (record = Record.new(field_values))
184
+ @records << record
185
+ when 'personal_records'
186
+ @personal_records << (record = PersonalRecords.new(field_values))
187
+ else
188
+ record = nil
112
189
  end
113
- str
190
+
191
+ record
114
192
  end
115
193
 
116
194
  end
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = DeviceInfo.rb -- Fit4Ruby - FIT file processing library for Ruby
5
+ #
6
+ # Copyright (c) 2014 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
+ class DeviceInfo < FitDataRecord
18
+
19
+ def initialize(field_values = {})
20
+ super('device_info')
21
+ set_field_values(field_values)
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = Event.rb -- Fit4Ruby - FIT file processing library for Ruby
5
+ #
6
+ # Copyright (c) 2014 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
+ class Event < FitDataRecord
18
+
19
+ def initialize(field_values = {})
20
+ super('event')
21
+ set_field_values(field_values)
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = FileCreator.rb -- Fit4Ruby - FIT file processing library for Ruby
5
+ #
6
+ # Copyright (c) 2014 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/version'
14
+ require 'fit4ruby/FitDataRecord'
15
+
16
+ module Fit4Ruby
17
+
18
+ class FileCreator < FitDataRecord
19
+
20
+ def initialize(field_values = {})
21
+ super('file_creator')
22
+ @software_version = VERSION.split('.').join('').to_i
23
+
24
+ set_field_values(field_values)
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby -w
2
2
  # encoding: UTF-8
3
3
  #
4
- # = FitFileId.rb -- Fit4Ruby - FIT file processing library for Ruby
4
+ # = FileId.rb -- Fit4Ruby - FIT file processing library for Ruby
5
5
  #
6
6
  # Copyright (c) 2014 by Chris Schlaeger <cs@taskjuggler.org>
7
7
  #
@@ -10,19 +10,21 @@
10
10
  # published by the Free Software Foundation.
11
11
  #
12
12
 
13
- require 'fit4ruby/Converters'
14
13
  require 'fit4ruby/FitDataRecord'
15
14
 
16
15
  module Fit4Ruby
17
16
 
18
- class FitFileId < FitDataRecord
17
+ class FileId < FitDataRecord
19
18
 
20
- def initialize
19
+ def initialize(field_values = {})
21
20
  super('file_id')
22
21
  @serial_number = 1234567890
23
- @time_created = Time.now
22
+ # Ignore the sub-seconds to avoid problems when comparing records.
23
+ @time_created = Time.at(Time.now.to_i)
24
24
  @manufacturer = 'development'
25
25
  @type = 'activity'
26
+
27
+ set_field_values(field_values)
26
28
  end
27
29
 
28
30
  end
@@ -19,11 +19,49 @@ module Fit4Ruby
19
19
 
20
20
  def initialize(record_id)
21
21
  @message = GlobalFitMessages.find_by_name(record_id)
22
- @renames = {}
22
+
23
+ # Create instance variables that correspond to every field of the
24
+ # corresponding FIT # data record.
25
+ @message.fields.each do |field_number, field|
26
+ create_instance_variable(field.name)
27
+ end
28
+ @timestamp = Time.now
29
+ end
30
+
31
+ def set_field_values(field_values)
32
+ field_values.each do |field, value|
33
+ set(field.to_s, value)
34
+ end
23
35
  end
24
36
 
25
- def rename(fit_var, var)
26
- @renames[fit_var] = var
37
+ def set(name, value)
38
+ ivar_name = '@' + name
39
+ unless instance_variable_defined?(ivar_name)
40
+ Log.warn("Unknown FIT record field '#{ivar_name}'")
41
+ return
42
+ end
43
+ instance_variable_set('@' + name, value)
44
+ end
45
+
46
+ def ==(fdr)
47
+ @message.fields.each do |field_number, field|
48
+ ivar_name = '@' + field.name
49
+ v1 = field.fit_to_native(field.native_to_fit(
50
+ instance_variable_get(ivar_name)))
51
+ v2 = field.fit_to_native(field.native_to_fit(
52
+ fdr.instance_variable_get(ivar_name)))
53
+
54
+ unless v1 == v2
55
+ Log.error "#{field.name}: #{v1} != #{v2}"
56
+ return false
57
+ end
58
+ end
59
+
60
+ true
61
+ end
62
+
63
+ def <=>(fdr)
64
+ @timestamp <=> fdr.timestamp
27
65
  end
28
66
 
29
67
  def write(io, id_mapper)
@@ -57,7 +95,7 @@ module Fit4Ruby
57
95
  # Fill the BinData::Struct object with the values from the corresponding
58
96
  # instance variables.
59
97
  @message.fields.each do |field_number, field|
60
- iv = "@#{@renames[field.name] || field.name}"
98
+ iv = "@#{field.name}"
61
99
  if instance_variable_defined?(iv) &&
62
100
  !(iv_value = instance_variable_get(iv)).nil?
63
101
  value = field.native_to_fit(iv_value)
@@ -73,6 +111,25 @@ module Fit4Ruby
73
111
  bd.write(io)
74
112
  end
75
113
 
114
+ def inspect
115
+ fields = {}
116
+ @message.fields.each do |field_number, field|
117
+ ivar_name = '@' + field.name
118
+ fields[field.name] = instance_variable_get(ivar_name)
119
+ end
120
+ fields.inspect
121
+ end
122
+
123
+ private
124
+
125
+ def create_instance_variable(name)
126
+ # Create a new instance variable for 'name'. We initialize it with a
127
+ # provided default value or nil.
128
+ instance_variable_set('@' + name, nil)
129
+ # And create an accessor method for it as well.
130
+ self.class.__send__(:attr_accessor, name)
131
+ end
132
+
76
133
  end
77
134
 
78
135
  end
@@ -15,7 +15,6 @@ require 'fit4ruby/FitHeader'
15
15
  require 'fit4ruby/FitRecord'
16
16
  require 'fit4ruby/FitFilter'
17
17
  require 'fit4ruby/FitMessageIdMapper'
18
- require 'fit4ruby/FitFileId'
19
18
  require 'fit4ruby/GlobalFitMessages'
20
19
  require 'fit4ruby/GlobalFitDictionaries'
21
20
 
@@ -32,7 +31,7 @@ module Fit4Ruby
32
31
  definitions = {}
33
32
  begin
34
33
  io = ::File.open(file_name, 'rb')
35
- rescue RuntimeError => e
34
+ rescue StandardError => e
36
35
  Log.critical("Cannot open FIT file '#{file_name}'", e)
37
36
  end
38
37
  header = FitHeader.read(io)
@@ -75,7 +74,6 @@ module Fit4Ruby
75
74
  # Move the pointer behind the header section.
76
75
  io.seek(start_pos)
77
76
  id_mapper = FitMessageIdMapper.new
78
- FitFileId.new.write(io, id_mapper)
79
77
  activity.write(io, id_mapper)
80
78
  end_pos = io.pos
81
79
 
@@ -23,7 +23,6 @@ module Fit4Ruby
23
23
 
24
24
  def add_global(id)
25
25
  unless (slot = @entries.index { |e| e.nil? })
26
- puts @entries.inspect
27
26
  # No more free slots. We have to find the least recently used one.
28
27
  slot = 0
29
28
  0.upto(15) do |i|
@@ -36,16 +36,7 @@ module Fit4Ruby
36
36
  def read(io, activity, filter = nil, fields_dump = nil)
37
37
  @message_record.read(io)
38
38
 
39
- obj = case @name
40
- when 'activity'
41
- activity
42
- when 'session'
43
- activity.new_session
44
- when 'record'
45
- activity.new_record
46
- else
47
- nil
48
- end
39
+ obj = @name == 'activity' ? activity : activity.new_fit_data_record(@name)
49
40
 
50
41
  @definition.fields.each do |field|
51
42
  value = @message_record[field.name].snapshot
@@ -12,6 +12,7 @@
12
12
 
13
13
  require 'fit4ruby/GlobalFitDictList'
14
14
  require 'fit4ruby/Converters'
15
+ require 'fit4ruby/FitDefinitionField'
15
16
 
16
17
  module Fit4Ruby
17
18
 
@@ -36,7 +37,7 @@ module Fit4Ruby
36
37
 
37
38
  if @opts.include?(:dict) &&
38
39
  (dict = GlobalFitDictionaries[@opts[:dict]])
39
- return [ dict.name(value) || "Undocumented value #{value}", nil ]
40
+ return dict.name(value) || "Undocumented value #{value}"
40
41
  end
41
42
 
42
43
  case @opts[:type]
@@ -72,19 +73,44 @@ module Fit4Ruby
72
73
  [ value, @opts[:unit] ]
73
74
  end
74
75
 
76
+ def fit_to_native(value)
77
+ return nil if value == FitDefinitionField.undefined_value(@type)
78
+
79
+ if @opts.include?(:dict) && (dict = GlobalFitDictionaries[@opts[:dict]])
80
+ return dict.name(value) || "Undocumented value #{value}"
81
+ end
82
+
83
+ value /= @opts[:scale].to_f if @opts[:scale]
84
+ value -= @opts[:offset] if @opts[:offset]
85
+
86
+ case @opts[:type]
87
+ when 'coordinate'
88
+ value *= 180.0 / 2147483648
89
+ when 'date_time'
90
+ value = fit_time_to_time(value)
91
+ end
92
+
93
+ value
94
+ end
95
+
75
96
  def native_to_fit(value)
76
- return nil if value.nil?
97
+ return FitDefinitionField.undefined_value(@type) if value.nil?
77
98
 
78
99
  if @opts.include?(:dict) && (dict = GlobalFitDictionaries[@opts[:dict]])
79
- return dict.value_by_name(value)
100
+ unless (dv = dict.value_by_name(value))
101
+ Log.error "Unknown value '#{value}' assigned to field #{@name}"
102
+ return FitDefinitionField.undefined_value(@type)
103
+ else
104
+ return dv
105
+ end
80
106
  end
81
107
 
82
- value = (value * @opts[:scale].to_f).to_i if @opts[:scale]
83
108
  value += @opts[:offset] if @opts[:offset]
109
+ value = (value * @opts[:scale].to_f).to_i if @opts[:scale]
84
110
 
85
111
  case @opts[:type]
86
112
  when 'coordinate'
87
- value *= 2147483648.0 / 180.0
113
+ value = (value * 2147483648.0 / 180.0).to_i
88
114
  when 'date_time'
89
115
  value = time_to_fit_time(value)
90
116
  end
@@ -43,7 +43,7 @@ module Fit4Ruby
43
43
  field 17, 'uint8', 'max_heart_rate', :unit => 'bpm'
44
44
  field 18, 'uint8', 'avg_running_cadence', :unit => 'strides/min'
45
45
  field 19, 'uint8', 'max_running_cadence', :unit => 'strides/min'
46
- field 22, 'uint16', 'total_ascent', :unit => 'm'
46
+ field 22, 'uint16', 'total_ascend', :unit => 'm'
47
47
  field 23, 'uint16', 'total_descent', :unit => 'm'
48
48
  field 24, 'uint8', 'total_training_effect', :scale => 10
49
49
  field 25, 'uint16', 'first_lap_index'
data/lib/fit4ruby/Lap.rb CHANGED
@@ -10,32 +10,81 @@
10
10
  # published by the Free Software Foundation.
11
11
  #
12
12
 
13
+ require 'fit4ruby/FitDataRecord'
14
+
13
15
  module Fit4Ruby
14
16
 
15
- class Lap
16
-
17
- def set(field, value)
18
- case field
19
- when 'start_time'
20
- @start_time = value
21
- when 'total_timer_time'
22
- @duration = value
23
- when 'avg_speed'
24
- @avg_speed = value
25
- when 'avg_heart_rate'
26
- @avg_heart_rate = value
27
- when 'max_heart_rate'
28
- @max_heart_rate = value
29
- when 'avg_vertical_oscillation'
30
- @avg_vertical_oscillation = value
31
- when 'avg_stance_time'
32
- @avg_stance_time = value
33
- when 'avg_running_cadence'
34
- @avg_running_cadence = 2 * value
35
- when 'avg_fraction_cadence'
36
- @avg_running_cadence += 2 * value
37
- else
17
+ class Lap < FitDataRecord
18
+
19
+ attr_reader :records
20
+
21
+ def initialize(records, field_values)
22
+ super('lap')
23
+ @records = records
24
+ set_field_values(field_values)
25
+ end
26
+
27
+ def check
28
+ ts = Time.parse('1989-12-31')
29
+ distance = nil
30
+ @records.each do |r|
31
+ Log.error "Record has no timestamp" unless r.timestamp
32
+ if r.timestamp < ts
33
+ Log.error "Record has earlier timestamp than previous record"
34
+ end
35
+ if r.distance
36
+ if distance && r.distance < distance
37
+ Log.error "Record has smaller distance than previous record"
38
+ end
39
+ distance = r.distance
40
+ end
41
+ ts = r.timestamp
42
+ end
43
+ end
44
+
45
+ def aggregate
46
+ return if @records.empty?
47
+
48
+ r = @records[0]
49
+ @start_time = r.timestamp
50
+ @start_position_lat = r.position_lat
51
+ @start_position_long = r.position_long
52
+
53
+ r = @records[-1]
54
+ @end_position_lat = r.position_lat
55
+ @end_position_long = r.position_long
56
+ @total_elapsed_time = r.timestamp - @start_time
57
+ @total_timer_time = @total_elapsed_time
58
+ @avg_speed = ((@total_distance.to_f / @total_elapsed_time) * 1000).to_i
59
+
60
+ @max_speed = 0
61
+ first_distance, last_distance = nil, nil
62
+ @records.each do |r|
63
+ first_distance = r.distance if first_distance.nil? && r.distance
64
+ last_distance = r.distance if r.distance
65
+ @max_speed = r.speed if r.speed && @max_speed < r.speed
66
+
67
+ if r.position_lat
68
+ if (@swc_lat.nil? || r.position_lat < @swc_lat)
69
+ @swc_lat = r.position_lat
70
+ end
71
+ if (@nec_lat.nil? || r.position_lat > @nec_lat)
72
+ @nec_lat = r.position_lat
73
+ end
74
+ end
75
+ if r.position_long
76
+ if (@swc_long.nil? || r.position_long < @swc_long)
77
+ @swc_long = r.position_long
78
+ end
79
+ if (@nec_long.nil? || r.position_long > @nec_long)
80
+ @nec_long = r.position_long
81
+ end
82
+ end
83
+
38
84
  end
85
+
86
+ @total_distance = last_distance && first_distance ?
87
+ last_distance - first_distance : 0
39
88
  end
40
89
 
41
90
  end
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = PersonalRecords.rb -- Fit4Ruby - FIT file processing library for Ruby
5
+ #
6
+ # Copyright (c) 2014 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
+ class PersonalRecords < FitDataRecord
18
+
19
+ def initialize(field_values = {})
20
+ super('personal_records')
21
+ set_field_values(field_values)
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+
@@ -10,40 +10,15 @@
10
10
  # published by the Free Software Foundation.
11
11
  #
12
12
 
13
- module Fit4Ruby
14
-
15
- class Record
13
+ require 'fit4ruby/FitDataRecord'
16
14
 
17
- attr_reader :timestamp, :latitude, :longitude, :altitude, :distance,
18
- :speed, :vertical_oscillation, :cadence, :stance_time
15
+ module Fit4Ruby
19
16
 
20
- def initialize
21
- end
17
+ class Record < FitDataRecord
22
18
 
23
- def set(field, value)
24
- case field
25
- when 'timestamp'
26
- @timestamp = value
27
- when 'position_lat'
28
- @latitude = value
29
- when 'position_long'
30
- @longitude = value
31
- when 'altitude'
32
- @altitude = value
33
- when 'distance'
34
- @distance = value
35
- when 'speed'
36
- @speed = value
37
- when 'vertical_oscillation'
38
- @vertical_oscillation = value
39
- when 'cadence'
40
- @cadence = 2 * value
41
- when 'fractional_cadence'
42
- @cadence += 2 * value if @cadence
43
- when 'stance_time'
44
- @stance_time = value
45
- else
46
- end
19
+ def initialize(field_values = {})
20
+ super('record')
21
+ set_field_values(field_values)
47
22
  end
48
23
 
49
24
  def pace
@@ -11,24 +11,31 @@
11
11
  #
12
12
 
13
13
  require 'fit4ruby/Converters'
14
+ require 'fit4ruby/FitDataRecord'
14
15
 
15
16
  module Fit4Ruby
16
17
 
17
- class Session
18
+ class Session < FitDataRecord
18
19
 
19
20
  include Converters
20
21
 
21
- attr_reader :start_time, :duration, :distance, :strides, :ascend,
22
- :descent, :calories, :avg_speed, :avg_heart_rate,
23
- :max_heart_rate, :avg_vertical_oscillation, :avg_stance_time,
24
- :avg_running_cadence, :avg_running_cadence, :training_effect,
25
- :first_lap_index, :num_laps
22
+ attr_reader :laps
26
23
 
27
- def initialize
28
- @laps = []
24
+ def initialize(laps, first_lap_index, field_values)
25
+ super('session')
26
+ @laps = laps
27
+ @first_lap_index = first_lap_index
28
+ @num_laps = @laps.length
29
+ set_field_values(field_values)
29
30
  end
30
31
 
31
32
  def check(activity)
33
+ unless @first_lap_index
34
+ Log.error 'first_lap_index is not set'
35
+ end
36
+ unless @num_laps
37
+ Log.error 'num_laps is not set'
38
+ end
32
39
  @first_lap_index.upto(@first_lap_index - @num_laps) do |i|
33
40
  if (lap = activity.lap[i])
34
41
  @laps << lap
@@ -37,71 +44,36 @@ module Fit4Ruby
37
44
  "the FIT file."
38
45
  end
39
46
  end
47
+ @laps.each { |l| l.check }
40
48
  end
41
49
 
42
- def set(field, value)
43
- return unless value
50
+ def aggregate
51
+ @total_distance = 0
52
+ @total_elapsed_time = 0
53
+ @laps.each do |lap|
54
+ lap.aggregate
55
+ @total_distance += lap.total_distance if lap.total_distance
56
+ @total_elapsed_time += lap.total_elapsed_time if lap.total_elapsed_time
57
+ end
58
+ if (l = @laps[0])
59
+ @start_time = l.start_time
60
+ @start_position_lat = l.start_position_lat
61
+ @start_position_long = l.start_position_long
62
+ end
63
+ if (l = @laps[-1])
64
+ @end_position_lat = l.end_position_lat
65
+ @end_position_long = l.end_position_long
66
+ end
44
67
 
45
- case field
46
- when 'start_time'
47
- @start_time = value
48
- when 'total_timer_time'
49
- @duration = value
50
- when 'total_distance'
51
- @distance = value
52
- when 'total_strides'
53
- @strides = value
54
- when 'total_ascent'
55
- @ascend = value
56
- when 'total_descent'
57
- @descent = value
58
- when 'total_calories'
59
- @calories = value
60
- when 'avg_speed'
61
- @avg_speed = value
62
- when 'avg_heart_rate'
63
- @avg_heart_rate = value
64
- when 'max_heart_rate'
65
- @max_heart_rate = value
66
- when 'avg_vertical_oscillation'
67
- @avg_vertical_oscillation = value
68
- when 'avg_stance_time'
69
- @avg_stance_time = value
70
- when 'avg_running_cadence'
71
- @avg_running_cadence = 2 * value
72
- when 'avg_fraction_cadence'
73
- @avg_running_cadence += 2 * value
74
- when 'total_training_effect'
75
- @training_effect = value
76
- when 'first_lap_index'
77
- @first_lap_index = value
78
- when 'num_laps'
79
- @num_laps = value
80
- else
68
+ if @total_distance && @total_elapsed_time
69
+ @avg_speed = @total_distance / @total_elapsed_time
81
70
  end
82
71
  end
83
72
 
84
73
  def avg_stride_length
85
- @distance / @strides
86
- end
74
+ return nil unless @total_strides
87
75
 
88
- def to_s
89
- <<"EOT"
90
- Date: #{@start_time}
91
- Distance: #{'%.2f' % (@distance / 1000.0)} km
92
- Time: #{secsToHMS(@duration)}
93
- Avg Pace: #{speedToPace(@avg_speed)} min/km
94
- Total Ascend: #{@ascend} m
95
- Total Descend: #{@descent} m
96
- Calories: #{@calories} kCal
97
- Avg HR: #{@avg_heart_rate} bpm
98
- Max HR: #{@max_heart_rate} bpm
99
- Training Effect: #{@training_effect}
100
- Avg Run Cadence: #{@avg_running_cadence.round} spm
101
- Avg Vertical Oscillation: #{'%.1f' % (@avg_vertical_oscillation / 10)} cm
102
- Avg Ground Contact Time: #{@avg_stance_time.round} ms
103
- Avg Stride Length: #{'%.2f' % (avg_stride_length / 2)} m
104
- EOT
76
+ @total_distance / @total_strides
105
77
  end
106
78
 
107
79
  end
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = UserProfile.rb -- Fit4Ruby - FIT file processing library for Ruby
5
+ #
6
+ # Copyright (c) 2014 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
+ class UserProfile < FitDataRecord
18
+
19
+ def initialize(field_values = {})
20
+ super('user_profile')
21
+ set_field_values(field_values)
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+
@@ -1,3 +1,3 @@
1
1
  module Fit4Ruby
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = FitFile_spec.rb -- Fit4Ruby - FIT file processing library for Ruby
5
+ #
6
+ # Copyright (c) 2014 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'
14
+
15
+ describe Fit4Ruby do
16
+
17
+ before(:each) do
18
+ a = Fit4Ruby::Activity.new
19
+ a.total_timer_time = 30 * 60
20
+ a.new_user_profile({ :age => 33, :height => 1.78, :weight => 73.0,
21
+ :gender => 'male', :activity_class => 4.0,
22
+ :max_hr => 178 })
23
+
24
+ a.new_event({ :event => 'timer', :event_type => 'start_time' })
25
+ a.new_device_info({ :device_index => 0 })
26
+ a.new_device_info({ :device_index => 1, :battery_status => 'ok' })
27
+ ts = Time.now
28
+ 0.upto(a.total_timer_time / 60) do |mins|
29
+ ts += 60
30
+ a.new_record({
31
+ :timestamp => ts,
32
+ :position_lat => 51.5512 - mins * 0.0008,
33
+ :position_long => 11.647 + mins * 0.002,
34
+ :distance => 200.0 * mins,
35
+ :altitude => 100 + mins * 0.5,
36
+ :speed => 3.1,
37
+ :vertical_oscillation => 9 + mins * 0.02,
38
+ :stance_time => 235.0 * mins * 0.01,
39
+ :stance_time_percent => 32.0,
40
+ :heart_rate => 140 + mins,
41
+ :cadence => 75,
42
+ :activity_type => 'running',
43
+ :fractional_cadence => (mins % 2) / 2.0
44
+ })
45
+
46
+ if mins > 0 && mins % 5 == 0
47
+ a.new_lap({ :timestamp => ts })
48
+ end
49
+ end
50
+ a.new_session({ :timestamp => ts })
51
+ a.new_event({ :timestamp => ts, :event => 'recovery_time',
52
+ :event_type => 'marker',
53
+ :data => 2160 })
54
+ a.new_event({ :timestamp => ts, :event => 'vo2max',
55
+ :event_type => 'marker', :data => 52 })
56
+ a.new_event({ :timestamp => ts, :event => 'timer',
57
+ :event_type => 'stop_all' })
58
+ a.new_device_info({ :timestamp => ts, :device_index => 0 })
59
+ ts += 1
60
+ a.new_device_info({ :timestamp => ts, :device_index => 1,
61
+ :battery_status => 'low' })
62
+ ts += 120
63
+ a.new_event({ :timestamp => ts, :event => 'recovery_hr',
64
+ :event_type => 'marker', :data => 132 })
65
+
66
+ a.aggregate
67
+
68
+ @activity = a
69
+ end
70
+
71
+ it 'should write a simple FIT file and read it back' do
72
+ fit_file = 'test.fit'
73
+
74
+ File.delete(fit_file) if File.exists?(fit_file)
75
+ Fit4Ruby.write(fit_file, @activity)
76
+ File.exists?(fit_file).should be_true
77
+
78
+ b = Fit4Ruby.read(fit_file)
79
+ b.should == @activity
80
+ #File.delete(fit_file)
81
+ end
82
+
83
+ end
84
+
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: 0.0.1
4
+ version: 0.0.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: 2014-07-20 00:00:00.000000000 Z
11
+ date: 2014-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bindata
@@ -84,11 +84,14 @@ files:
84
84
  - lib/fit4ruby.rb
85
85
  - lib/fit4ruby/Activity.rb
86
86
  - lib/fit4ruby/Converters.rb
87
+ - lib/fit4ruby/DeviceInfo.rb
88
+ - lib/fit4ruby/Event.rb
89
+ - lib/fit4ruby/FileCreator.rb
90
+ - lib/fit4ruby/FileId.rb
87
91
  - lib/fit4ruby/FitDataRecord.rb
88
92
  - lib/fit4ruby/FitDefinition.rb
89
93
  - lib/fit4ruby/FitDefinitionField.rb
90
94
  - lib/fit4ruby/FitFile.rb
91
- - lib/fit4ruby/FitFileId.rb
92
95
  - lib/fit4ruby/FitFilter.rb
93
96
  - lib/fit4ruby/FitHeader.rb
94
97
  - lib/fit4ruby/FitMessageIdMapper.rb
@@ -101,14 +104,16 @@ files:
101
104
  - lib/fit4ruby/GlobalFitMessages.rb
102
105
  - lib/fit4ruby/Lap.rb
103
106
  - lib/fit4ruby/Log.rb
107
+ - lib/fit4ruby/PersonalRecords.rb
104
108
  - lib/fit4ruby/Record.rb
105
109
  - lib/fit4ruby/Session.rb
110
+ - lib/fit4ruby/UserProfile.rb
106
111
  - lib/fit4ruby/version.rb
112
+ - spec/FitFile_spec.rb
107
113
  - tasks/changelog.rake
108
114
  - tasks/gem.rake
109
115
  - tasks/rdoc.rake
110
116
  - tasks/test.rake
111
- - test/FitFile_spec.rb
112
117
  homepage: https://github.com/scrapper/fit4ruby
113
118
  licenses:
114
119
  - GNU GPL version 2
@@ -134,4 +139,4 @@ signing_key:
134
139
  specification_version: 4
135
140
  summary: Library to read GARMIN FIT files.
136
141
  test_files:
137
- - test/FitFile_spec.rb
142
+ - spec/FitFile_spec.rb
data/test/FitFile_spec.rb DELETED
@@ -1,31 +0,0 @@
1
- #!/usr/bin/env ruby -w
2
- # encoding: UTF-8
3
- #
4
- # = FitFile_spec.rb -- Fit4Ruby - FIT file processing library for Ruby
5
- #
6
- # Copyright (c) 2014 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'
14
-
15
- describe Fit4Ruby do
16
-
17
- it 'should write a simple .fit file' do
18
- fit_file = 'test.fit'
19
-
20
- a = Fit4Ruby::Activity.new
21
- a.start_time = Time.parse('2014-07-14-21:00')
22
- a.duration = 30 * 60
23
- Fit4Ruby.write(fit_file, a)
24
- b = Fit4Ruby.read(fit_file)
25
- a.start_time.should == b.start_time
26
- a.duration.should == b.duration
27
- File.delete(fit_file)
28
- end
29
-
30
- end
31
-