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 +4 -4
- data/COPYING +72 -13
- data/Rakefile +3 -1
- data/lib/fit4ruby.rb +2 -2
- data/lib/fit4ruby/Activity.rb +137 -59
- data/lib/fit4ruby/DeviceInfo.rb +27 -0
- data/lib/fit4ruby/Event.rb +27 -0
- data/lib/fit4ruby/FileCreator.rb +30 -0
- data/lib/fit4ruby/{FitFileId.rb → FileId.rb} +7 -5
- data/lib/fit4ruby/FitDataRecord.rb +61 -4
- data/lib/fit4ruby/FitFile.rb +1 -3
- data/lib/fit4ruby/FitMessageIdMapper.rb +0 -1
- data/lib/fit4ruby/FitMessageRecord.rb +1 -10
- data/lib/fit4ruby/GlobalFitMessage.rb +31 -5
- data/lib/fit4ruby/GlobalFitMessages.rb +1 -1
- data/lib/fit4ruby/Lap.rb +72 -23
- data/lib/fit4ruby/PersonalRecords.rb +27 -0
- data/lib/fit4ruby/Record.rb +6 -31
- data/lib/fit4ruby/Session.rb +37 -65
- data/lib/fit4ruby/UserProfile.rb +27 -0
- data/lib/fit4ruby/version.rb +1 -1
- data/spec/FitFile_spec.rb +84 -0
- metadata +10 -5
- data/test/FitFile_spec.rb +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 49de464c7356d1b55144fac48b0f6150371e29a9
|
4
|
+
data.tar.gz: 4b5e2057123c3e4a62c6f2335182cfd6cf8aa8ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b8688224199cac6c1b024711d680bb98b33cb761e46d36af2d83e61aa04701a9f182fc70f5dd442f4a8311c6be43f7a6bf1ccf063034fd31e751191dfff8c2a2
|
7
|
+
data.tar.gz: 4c0bdd82555fb57d705d8992c21aa48a01b689d9596b61199894a89604d24dd4e159e55e86fcdd8b1327b4070270b688a78e0628e2caaad929f0308af4f88c4a
|
data/COPYING
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
GNU GENERAL PUBLIC LICENSE
|
2
|
+
Version 2, June 1991
|
3
3
|
|
4
|
-
Copyright (C) 1989, 1991 Free Software Foundation, Inc
|
5
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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 =>
|
18
|
+
task :default => :spec
|
19
|
+
task :test => :spec
|
18
20
|
|
19
21
|
desc 'Run all unit and spec tests'
|
data/lib/fit4ruby.rb
CHANGED
data/lib/fit4ruby/Activity.rb
CHANGED
@@ -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/
|
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 :
|
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 @
|
41
|
-
Log.error "Activity has no valid
|
52
|
+
unless @timestamp && @timestamp >= Time.parse('1990-01-01')
|
53
|
+
Log.error "Activity has no valid timestamp"
|
42
54
|
end
|
43
|
-
unless @
|
44
|
-
Log.error "Activity has no valid
|
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
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
68
|
-
|
69
|
-
|
90
|
+
def vo2max
|
91
|
+
@events.each do |e|
|
92
|
+
return e.data if e.event == 'vo2max'
|
93
|
+
end
|
70
94
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
93
|
-
|
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
|
98
|
-
|
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
|
103
|
-
|
104
|
-
|
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
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
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
|
-
|
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
|
-
# =
|
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
|
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
|
-
|
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
|
-
|
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
|
26
|
-
|
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 = "@#{
|
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
|
data/lib/fit4ruby/FitFile.rb
CHANGED
@@ -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
|
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
|
|
@@ -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 =
|
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
|
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
|
97
|
+
return FitDefinitionField.undefined_value(@type) if value.nil?
|
77
98
|
|
78
99
|
if @opts.include?(:dict) && (dict = GlobalFitDictionaries[@opts[:dict]])
|
79
|
-
|
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
|
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', '
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
+
|
data/lib/fit4ruby/Record.rb
CHANGED
@@ -10,40 +10,15 @@
|
|
10
10
|
# published by the Free Software Foundation.
|
11
11
|
#
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
class Record
|
13
|
+
require 'fit4ruby/FitDataRecord'
|
16
14
|
|
17
|
-
|
18
|
-
:speed, :vertical_oscillation, :cadence, :stance_time
|
15
|
+
module Fit4Ruby
|
19
16
|
|
20
|
-
|
21
|
-
end
|
17
|
+
class Record < FitDataRecord
|
22
18
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
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
|
data/lib/fit4ruby/Session.rb
CHANGED
@@ -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 :
|
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
|
-
|
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
|
43
|
-
|
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
|
-
|
46
|
-
|
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
|
-
|
86
|
-
end
|
74
|
+
return nil unless @total_strides
|
87
75
|
|
88
|
-
|
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
|
+
|
data/lib/fit4ruby/version.rb
CHANGED
@@ -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.
|
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-
|
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
|
-
-
|
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
|
-
|