fit4ruby 0.0.7 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/fit4ruby/Activity.rb +37 -14
- data/lib/fit4ruby/DataSources.rb +35 -0
- data/lib/fit4ruby/DeviceInfo.rb +36 -1
- data/lib/fit4ruby/Event.rb +14 -1
- data/lib/fit4ruby/FileNameCoder.rb +67 -0
- data/lib/fit4ruby/FitDataRecord.rb +10 -2
- data/lib/fit4ruby/FitDefinition.rb +1 -1
- data/lib/fit4ruby/FitFile.rb +4 -4
- data/lib/fit4ruby/GeoMath.rb +55 -0
- data/lib/fit4ruby/GlobalFitDictionaries.rb +1 -1
- data/lib/fit4ruby/GlobalFitMessage.rb +1 -1
- data/lib/fit4ruby/GlobalFitMessages.rb +9 -9
- data/lib/fit4ruby/Lap.rb +8 -5
- data/lib/fit4ruby/Log.rb +25 -11
- data/lib/fit4ruby/Session.rb +4 -5
- data/lib/fit4ruby/version.rb +1 -1
- data/spec/FileNameCoder_spec.rb +61 -0
- data/spec/FitFile_spec.rb +33 -8
- data/spec/GeoMath_spec.rb +34 -0
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f651054b5f16738a2ca5fc4146cdb817c4ef102
|
4
|
+
data.tar.gz: b1af399d6335bcf932dfdeb3b35897191bfc6290
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 773e7c729a6ecae371b03269da9da1312f9ca7da35f3e14e6cc6e2c8429955d5f4ea0f965a975bde69368853d9a7555280f4da9a630dd8e1525f827e090b06d1
|
7
|
+
data.tar.gz: 2e4ea74d55a4496977133a15e0ccb5bf0902b4104c32587e783c3427c175cc123c70a498a8e0b0a56cc28451e3fb95b6c0a509b9b28431ff376194b2a94c0932
|
data/lib/fit4ruby/Activity.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# = Activity.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
|
@@ -14,6 +14,7 @@ require 'fit4ruby/FitDataRecord'
|
|
14
14
|
require 'fit4ruby/FileId'
|
15
15
|
require 'fit4ruby/FileCreator'
|
16
16
|
require 'fit4ruby/DeviceInfo'
|
17
|
+
require 'fit4ruby/DataSources'
|
17
18
|
require 'fit4ruby/UserProfile'
|
18
19
|
require 'fit4ruby/Session'
|
19
20
|
require 'fit4ruby/Lap'
|
@@ -28,8 +29,9 @@ module Fit4Ruby
|
|
28
29
|
# equivalents of the message record structures used in the FIT file.
|
29
30
|
class Activity < FitDataRecord
|
30
31
|
|
31
|
-
attr_accessor :file_id, :file_creator, :device_infos, :
|
32
|
-
:sessions, :laps, :records,
|
32
|
+
attr_accessor :file_id, :file_creator, :device_infos, :data_sources,
|
33
|
+
:user_profiles, :sessions, :laps, :records,
|
34
|
+
:events, :personal_records
|
33
35
|
|
34
36
|
# Create a new Activity object.
|
35
37
|
# @param field_values [Hash] A Hash that provides initial values for
|
@@ -42,6 +44,7 @@ module Fit4Ruby
|
|
42
44
|
@file_id = FileId.new
|
43
45
|
@file_creator = FileCreator.new
|
44
46
|
@device_infos = []
|
47
|
+
@data_sources = []
|
45
48
|
@user_profiles = []
|
46
49
|
@events = []
|
47
50
|
@sessions = []
|
@@ -61,23 +64,25 @@ module Fit4Ruby
|
|
61
64
|
# objects. Any errors will be reported via the Log object.
|
62
65
|
def check
|
63
66
|
unless @timestamp && @timestamp >= Time.parse('1990-01-01T00:00:00+00:00')
|
64
|
-
Log.
|
67
|
+
Log.fatal "Activity has no valid timestamp"
|
65
68
|
end
|
66
69
|
unless @total_timer_time
|
67
|
-
Log.
|
70
|
+
Log.fatal "Activity has no valid total_timer_time"
|
68
71
|
end
|
72
|
+
unless @device_infos.length > 0
|
73
|
+
Log.fatal "Activity must have at least one device_info section"
|
74
|
+
end
|
75
|
+
@device_infos.each.with_index { |d, index| d.check(index) }
|
69
76
|
unless @num_sessions == @sessions.count
|
70
|
-
Log.
|
77
|
+
Log.fatal "Activity record requires #{@num_sessions}, but "
|
71
78
|
"#{@sessions.length} session records were found in the "
|
72
79
|
"FIT file."
|
73
80
|
end
|
74
|
-
@sessions.each { |s| s.check(self) }
|
75
81
|
# Laps must have a consecutively growing message index.
|
76
82
|
@laps.each.with_index do |lap, index|
|
77
|
-
|
78
|
-
Log.error "Lap #{index} has wrong message_index #{lap.message_index}"
|
79
|
-
end
|
83
|
+
lap.check(index)
|
80
84
|
end
|
85
|
+
@sessions.each { |s| s.check(self) }
|
81
86
|
end
|
82
87
|
|
83
88
|
# Convenience method that aggregates all the distances from the included
|
@@ -196,8 +201,8 @@ module Fit4Ruby
|
|
196
201
|
@file_id.write(io, id_mapper)
|
197
202
|
@file_creator.write(io, id_mapper)
|
198
203
|
|
199
|
-
(@device_infos + @
|
200
|
-
@records + @personal_records).sort.each do |s|
|
204
|
+
(@device_infos + @data_sources + @user_profiles + @events +
|
205
|
+
@sessions + @laps + @records + @personal_records).sort.each do |s|
|
201
206
|
s.write(io, id_mapper)
|
202
207
|
end
|
203
208
|
super
|
@@ -229,6 +234,14 @@ module Fit4Ruby
|
|
229
234
|
new_fit_data_record('device_info', field_values)
|
230
235
|
end
|
231
236
|
|
237
|
+
# Add a new SourceData to the Activity.
|
238
|
+
# @param field_values [Hash] A Hash that provides initial values for
|
239
|
+
# certain fields of the FitDataRecord.
|
240
|
+
# @return [SourceData]
|
241
|
+
def new_data_sources(field_values = {})
|
242
|
+
new_fit_data_record('data_sources', field_values)
|
243
|
+
end
|
244
|
+
|
232
245
|
# Add a new UserProfile to the Activity.
|
233
246
|
# @param field_values [Hash] A Hash that provides initial values for
|
234
247
|
# certain fields of the FitDataRecord.
|
@@ -291,7 +304,9 @@ module Fit4Ruby
|
|
291
304
|
def ==(a)
|
292
305
|
super(a) && @file_id == a.file_id &&
|
293
306
|
@file_creator == a.file_creator &&
|
294
|
-
@device_infos == a.device_infos &&
|
307
|
+
@device_infos == a.device_infos &&
|
308
|
+
@data_sources == a.data_sources &&
|
309
|
+
@user_profiles == a.user_profiles &&
|
295
310
|
@events == a.events &&
|
296
311
|
@sessions == a.sessions && personal_records == a.personal_records
|
297
312
|
end
|
@@ -310,14 +325,21 @@ module Fit4Ruby
|
|
310
325
|
@file_creator = (record = FileCreator.new(field_values))
|
311
326
|
when 'device_info'
|
312
327
|
@device_infos << (record = DeviceInfo.new(field_values))
|
328
|
+
when 'data_sources'
|
329
|
+
@data_sources << (record = DataSources.new(field_values))
|
313
330
|
when 'user_profile'
|
314
331
|
@user_profiles << (record = UserProfile.new(field_values))
|
315
332
|
when 'event'
|
316
333
|
@events << (record = Event.new(field_values))
|
317
334
|
when 'session'
|
318
335
|
unless @cur_lap_records.empty?
|
336
|
+
# Copy selected fields from section to lap.
|
337
|
+
lap_field_values = {}
|
338
|
+
[ :timestamp, :sport ].each do |f|
|
339
|
+
lap_field_values[f] = field_values[f] if field_values.include?(f)
|
340
|
+
end
|
319
341
|
# Ensure that all previous records have been assigned to a lap.
|
320
|
-
record = create_new_lap(
|
342
|
+
record = create_new_lap(lap_field_values)
|
321
343
|
end
|
322
344
|
@num_sessions += 1
|
323
345
|
@sessions << (record = Session.new(@cur_session_laps, @lap_counter,
|
@@ -341,6 +363,7 @@ module Fit4Ruby
|
|
341
363
|
|
342
364
|
def create_new_lap(field_values)
|
343
365
|
lap = Lap.new(@cur_lap_records, @laps.last, field_values)
|
366
|
+
lap.message_index = @lap_counter - 1
|
344
367
|
@lap_counter += 1
|
345
368
|
@cur_session_laps << lap
|
346
369
|
@laps << lap
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
# encoding: UTF-8
|
3
|
+
#
|
4
|
+
# = DataSources.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/FitDataRecord'
|
14
|
+
|
15
|
+
module Fit4Ruby
|
16
|
+
|
17
|
+
# The DataSources objects are generated by message 22. This message type is
|
18
|
+
# not documented by Garmin and hence the message name and all field names
|
19
|
+
# and their interpretation are guessed. Unless this message gets officially
|
20
|
+
# documented, all names are subject to change. Even minor version changes of
|
21
|
+
# this library can break the API for this message type.
|
22
|
+
class DataSources < FitDataRecord
|
23
|
+
|
24
|
+
def initialize(field_values = {})
|
25
|
+
super('data_sources')
|
26
|
+
set_field_values(field_values)
|
27
|
+
end
|
28
|
+
|
29
|
+
def check(index)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
data/lib/fit4ruby/DeviceInfo.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# = DeviceInfo.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
|
@@ -21,6 +21,41 @@ module Fit4Ruby
|
|
21
21
|
set_field_values(field_values)
|
22
22
|
end
|
23
23
|
|
24
|
+
# Ensure that FitDataRecords have a deterministic sequence. Device infos
|
25
|
+
# are sorted by device_index.
|
26
|
+
def <=>(fdr)
|
27
|
+
@timestamp == fdr.timestamp ?
|
28
|
+
@message.name == fdr.message.name ?
|
29
|
+
@device_index <=> fdr.device_index :
|
30
|
+
RecordOrder.index(@message.name) <=>
|
31
|
+
RecordOrder.index(fdr.message.name) :
|
32
|
+
@timestamp <=> fdr.timestamp
|
33
|
+
end
|
34
|
+
|
35
|
+
def check(index)
|
36
|
+
unless @device_index
|
37
|
+
Log.fatal 'device info record must have a device_index'
|
38
|
+
end
|
39
|
+
if @device_index == 0
|
40
|
+
unless @manufacturer
|
41
|
+
Log.fatal 'device info record 0 must have a manufacturer field set'
|
42
|
+
end
|
43
|
+
if @manufacturer == 'garmin'
|
44
|
+
unless @garmin_product
|
45
|
+
Log.fatal 'device info record 0 must have a garman_product ' +
|
46
|
+
'field set'
|
47
|
+
end
|
48
|
+
else
|
49
|
+
unless @product
|
50
|
+
Log.fatal 'device info record 0 must have a product field set'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
if @serial_number.nil?
|
54
|
+
Log.fatal 'device info record 0 must have a serial number set'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
24
59
|
end
|
25
60
|
|
26
61
|
end
|
data/lib/fit4ruby/Event.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# = Event.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
|
@@ -21,6 +21,19 @@ module Fit4Ruby
|
|
21
21
|
set_field_values(field_values)
|
22
22
|
end
|
23
23
|
|
24
|
+
# Ensure that FitDataRecords have a deterministic sequence. Events are
|
25
|
+
# sorted by event_type and then event.
|
26
|
+
def <=>(fdr)
|
27
|
+
@timestamp == fdr.timestamp ?
|
28
|
+
@message.name == fdr.message.name ?
|
29
|
+
@event_type == fdr.event_type ?
|
30
|
+
@event <=> fdr.event :
|
31
|
+
@event_type <=> fdr.event_type :
|
32
|
+
RecordOrder.index(@message.name) <=>
|
33
|
+
RecordOrder.index(fdr.message.name) :
|
34
|
+
@timestamp <=> fdr.timestamp
|
35
|
+
end
|
36
|
+
|
24
37
|
end
|
25
38
|
|
26
39
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
# encoding: UTF-8
|
3
|
+
#
|
4
|
+
# = FileNameCoder.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
|
+
module Fit4Ruby
|
14
|
+
|
15
|
+
# This class provides encoder and decoder for the FIT file names typically
|
16
|
+
# used for activies and monitor data files.
|
17
|
+
class FileNameCoder
|
18
|
+
|
19
|
+
CodeBook = 0.upto(9).map{ |i| (?0.ord + i).chr} +
|
20
|
+
0.upto(25).map{ |i|(?A.ord + i).chr}
|
21
|
+
|
22
|
+
# Convert a Time to a corresponding FIT file name.
|
23
|
+
# @param [Time] time stamp
|
24
|
+
# @return [String] FIT file name with extension '.FIT'
|
25
|
+
def FileNameCoder::encode(time)
|
26
|
+
utc = time.utc
|
27
|
+
if (year = utc.year) < 2010 || year > 2033
|
28
|
+
raise ArgumentError, "Year must be between 2010 and 2033"
|
29
|
+
end
|
30
|
+
year = CodeBook[year - 2010]
|
31
|
+
month = CodeBook[utc.month]
|
32
|
+
day = CodeBook[utc.day]
|
33
|
+
hour = CodeBook[utc.hour]
|
34
|
+
minutes = "%02d" % utc.min
|
35
|
+
seconds = "%02d" % utc.sec
|
36
|
+
|
37
|
+
year + month + day + hour + minutes + seconds + '.FIT'
|
38
|
+
end
|
39
|
+
|
40
|
+
# Convert a FIT file name into the corresponding Time value.
|
41
|
+
# @param [String] FIT file name. This can be a full path name but must end
|
42
|
+
# with a '.FIT' extension.
|
43
|
+
# @return [Time] corresponding Time value
|
44
|
+
def FileNameCoder::decode(file_name)
|
45
|
+
base = File.basename(file_name.upcase)
|
46
|
+
unless /\A[0-9A-Z]{4}[0-9]{4}\.FIT\z/ =~ base
|
47
|
+
raise ArgumentError, "#{file_name} is not a valid FIT file name"
|
48
|
+
end
|
49
|
+
|
50
|
+
year = 2010 + CodeBook.index(base[0])
|
51
|
+
month = CodeBook.index(base[1])
|
52
|
+
day = CodeBook.index(base[2])
|
53
|
+
hour = CodeBook.index(base[3])
|
54
|
+
minutes = base[4,2].to_i
|
55
|
+
seconds = base[6,2].to_i
|
56
|
+
if month == 0 || month > 12 || day == 0 || day > 31 ||
|
57
|
+
hour >= 24 || minutes >= 60 || seconds >= 60
|
58
|
+
raise ArgumentError, "#{file_name} is not a valid FIT file name"
|
59
|
+
end
|
60
|
+
|
61
|
+
Time.new(year, month, day, hour, minutes, seconds, "+00:00")
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# = FitDataRecord.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
|
@@ -19,6 +19,11 @@ module Fit4Ruby
|
|
19
19
|
|
20
20
|
include Converters
|
21
21
|
|
22
|
+
RecordOrder = [ 'user_profile', 'device_info', 'data_sources', 'event',
|
23
|
+
'record', 'lap', 'session', 'personal_records' ]
|
24
|
+
|
25
|
+
attr_reader :message
|
26
|
+
|
22
27
|
def initialize(record_id)
|
23
28
|
@message = GlobalFitMessages.find_by_name(record_id)
|
24
29
|
|
@@ -91,7 +96,10 @@ module Fit4Ruby
|
|
91
96
|
end
|
92
97
|
|
93
98
|
def <=>(fdr)
|
94
|
-
@timestamp
|
99
|
+
@timestamp == fdr.timestamp ?
|
100
|
+
RecordOrder.index(@message.name) <=>
|
101
|
+
RecordOrder.index(fdr.message.name) :
|
102
|
+
@timestamp <=> fdr.timestamp
|
95
103
|
end
|
96
104
|
|
97
105
|
def write(io, id_mapper)
|
data/lib/fit4ruby/FitFile.rb
CHANGED
@@ -33,7 +33,7 @@ module Fit4Ruby
|
|
33
33
|
begin
|
34
34
|
io = ::File.open(file_name, 'rb')
|
35
35
|
rescue StandardError => e
|
36
|
-
Log.
|
36
|
+
Log.fatal "Cannot open FIT file '#{file_name}': #{e.message}"
|
37
37
|
end
|
38
38
|
header = FitHeader.read(io)
|
39
39
|
header.check
|
@@ -66,7 +66,7 @@ module Fit4Ruby
|
|
66
66
|
begin
|
67
67
|
io = ::File.open(file_name, 'wb+')
|
68
68
|
rescue StandardError => e
|
69
|
-
Log.
|
69
|
+
Log.fatal "Cannot open FIT file '#{file_name}': #{e.message}"
|
70
70
|
end
|
71
71
|
|
72
72
|
# Create a header object, but don't yet write it into the file.
|
@@ -104,8 +104,8 @@ module Fit4Ruby
|
|
104
104
|
io.seek(start_pos)
|
105
105
|
|
106
106
|
unless crc == crc_ref
|
107
|
-
Log.
|
108
|
-
|
107
|
+
Log.fatal "Checksum error in file '#{@file_name}'. " +
|
108
|
+
"Computed #{"%04X" % crc} instead of #{"%04X" % crc_ref}."
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
@@ -0,0 +1,55 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
# encoding: UTF-8
|
3
|
+
#
|
4
|
+
# = Activity.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
|
+
module Fit4Ruby
|
14
|
+
|
15
|
+
module GeoMath
|
16
|
+
|
17
|
+
# This method uses the ellipsoidal earth projected to a plane formula
|
18
|
+
# prescribed by the FCC in 47 CFR 73.208 for distances not exceeding 475
|
19
|
+
# km /295 miles.
|
20
|
+
# @param p1_lat Latitude of the first point in polar degrees
|
21
|
+
# @param p1_lon Longitude of the first point in polar degrees
|
22
|
+
# @param p2_lat Latitude of the second point in polar degrees
|
23
|
+
# @param p2_lon Longitude of the second point in polar degrees
|
24
|
+
# @return Distance in meters
|
25
|
+
def GeoMath.distance(p1_lat, p1_lon, p2_lat, p2_lon)
|
26
|
+
# Difference in latitude and longitude
|
27
|
+
delta_lat = p2_lat - p1_lat
|
28
|
+
delta_lon = p2_lon - p1_lon
|
29
|
+
|
30
|
+
# Mean latitude
|
31
|
+
mean_lat = (p1_lat + p2_lat) / 2
|
32
|
+
|
33
|
+
# kilometers per degree of latitude difference
|
34
|
+
k1 = 111.13209 - 0.56606 * cos(2 * mean_lat) +
|
35
|
+
0.00120 * cos(4 * mean_lat)
|
36
|
+
# kilometers per degree of longitude difference
|
37
|
+
k2 = 111.41513 * cos(mean_lat) -
|
38
|
+
0.09455 * cos(3 * mean_lat) +
|
39
|
+
0.00012 * cos(5 * mean_lat)
|
40
|
+
|
41
|
+
Math.sqrt(((k1 * delta_lat)) ** 2 + (k2 * delta_lon) ** 2) * 1000.0
|
42
|
+
end
|
43
|
+
|
44
|
+
def GeoMath.cos(deg)
|
45
|
+
Math.cos(deg_to_rad(deg))
|
46
|
+
end
|
47
|
+
|
48
|
+
def GeoMath.deg_to_rad(deg)
|
49
|
+
deg * Math::PI / 180
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# = GlobalFitMessage.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
|
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# = GlobalFitMessages.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
|
@@ -255,16 +255,16 @@ module Fit4Ruby
|
|
255
255
|
field 253, 'uint32', 'timestamp', :type => 'date_time'
|
256
256
|
|
257
257
|
# Possibly which device is used as metering source.
|
258
|
-
# Not documented in FIT SDK, so
|
259
|
-
message 22, '
|
260
|
-
field 0, 'uint8', '
|
261
|
-
field 1, 'uint8', '
|
258
|
+
# Not documented in FIT SDK, so the field names are all guesses right now.
|
259
|
+
message 22, 'data_sources'
|
260
|
+
field 0, 'uint8', 'distance'
|
261
|
+
field 1, 'uint8', 'speed'
|
262
262
|
field 2, 'uint8', 'cadence'
|
263
|
-
field 3, 'uint8', '
|
263
|
+
field 3, 'uint8', 'elevation'
|
264
264
|
field 4, 'uint8', 'heart_rate'
|
265
|
-
field 5, 'enum', '
|
266
|
-
field 6, 'uint8', '
|
267
|
-
field 14, 'uint8', '
|
265
|
+
field 5, 'enum', 'mode' # 0 or 3 seen, unknown meaning
|
266
|
+
field 6, 'uint8', 'power' # First found in FR920XT
|
267
|
+
field 14, 'uint8', 'calories' # First found in FR920XT
|
268
268
|
field 253, 'uint32', 'timestamp', :type => 'date_time'
|
269
269
|
|
270
270
|
message 23, 'device_info'
|
data/lib/fit4ruby/Lap.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# = Lap.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
|
@@ -43,17 +43,20 @@ module Fit4Ruby
|
|
43
43
|
set_field_values(field_values)
|
44
44
|
end
|
45
45
|
|
46
|
-
def check
|
46
|
+
def check(index)
|
47
|
+
unless @message_index == index
|
48
|
+
Log.fatal "message_index must be #{index}, not #{@message_index}"
|
49
|
+
end
|
47
50
|
ts = Time.parse('1989-12-31')
|
48
51
|
distance = nil
|
49
52
|
@records.each do |r|
|
50
|
-
Log.
|
53
|
+
Log.fatal "Record has no timestamp" unless r.timestamp
|
51
54
|
if r.timestamp < ts
|
52
|
-
Log.
|
55
|
+
Log.fatal "Record has earlier timestamp than previous record"
|
53
56
|
end
|
54
57
|
if r.distance
|
55
58
|
if distance && r.distance < distance
|
56
|
-
Log.
|
59
|
+
Log.fatal "Record has smaller distance than previous record"
|
57
60
|
end
|
58
61
|
distance = r.distance
|
59
62
|
end
|
data/lib/fit4ruby/Log.rb
CHANGED
@@ -10,7 +10,9 @@
|
|
10
10
|
# published by the Free Software Foundation.
|
11
11
|
#
|
12
12
|
|
13
|
+
require 'monitor'
|
13
14
|
require 'logger'
|
15
|
+
require 'singleton'
|
14
16
|
|
15
17
|
module Fit4Ruby
|
16
18
|
|
@@ -18,24 +20,36 @@ module Fit4Ruby
|
|
18
20
|
# errors.
|
19
21
|
class Error < StandardError ; end
|
20
22
|
|
21
|
-
|
23
|
+
# The ILogger class is a singleton that provides a common logging mechanism
|
24
|
+
# to all objects. It exposes essentially the same interface as the Logger
|
25
|
+
# class, just as a singleton and with some additional methods like 'fatal'
|
26
|
+
# and 'cricital'.
|
27
|
+
class ILogger < Monitor
|
22
28
|
|
23
|
-
|
24
|
-
|
25
|
-
|
29
|
+
include Singleton
|
30
|
+
|
31
|
+
@@logger = Logger.new(STDOUT)
|
32
|
+
|
33
|
+
# Pass all calls to unknown methods to the @@logger object.
|
34
|
+
def method_missing(method, *args, &block)
|
35
|
+
@@logger.send(method, *args, &block)
|
26
36
|
end
|
27
37
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
38
|
+
# Make it properly introspectable.
|
39
|
+
def respond_to?(method, include_private = false)
|
40
|
+
@@logger.respond_to?(method)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Print an error message via the Logger and raise and Fit4Ruby::Error.
|
44
|
+
# code 1.
|
45
|
+
def fatal(msg)
|
46
|
+
@@logger.error(msg)
|
47
|
+
raise Error, msg
|
34
48
|
end
|
35
49
|
|
36
50
|
end
|
37
51
|
|
38
|
-
Log = ILogger.
|
52
|
+
Log = ILogger.instance
|
39
53
|
Log.level = Logger::WARN
|
40
54
|
Log.formatter = proc do |severity, time, progname, msg|
|
41
55
|
msg + "\n"
|
data/lib/fit4ruby/Session.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# = Session.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
|
@@ -54,20 +54,19 @@ module Fit4Ruby
|
|
54
54
|
# are reported via the Log object.
|
55
55
|
def check(activity)
|
56
56
|
unless @first_lap_index
|
57
|
-
Log.
|
57
|
+
Log.fatal 'first_lap_index is not set'
|
58
58
|
end
|
59
59
|
unless @num_laps
|
60
|
-
Log.
|
60
|
+
Log.fatal 'num_laps is not set'
|
61
61
|
end
|
62
62
|
@first_lap_index.upto(@first_lap_index - @num_laps) do |i|
|
63
63
|
if (lap = activity.lap[i])
|
64
64
|
@laps << lap
|
65
65
|
else
|
66
|
-
Log.
|
66
|
+
Log.fatal "Session references lap #{i} which is not contained in "
|
67
67
|
"the FIT file."
|
68
68
|
end
|
69
69
|
end
|
70
|
-
@laps.each { |l| l.check }
|
71
70
|
end
|
72
71
|
|
73
72
|
# Return true if the session contains geographical location data.
|
data/lib/fit4ruby/version.rb
CHANGED
@@ -0,0 +1,61 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
# encoding: UTF-8
|
3
|
+
#
|
4
|
+
# = FileNameCoder_spec.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/FileNameCoder'
|
14
|
+
|
15
|
+
describe Fit4Ruby::FileNameCoder do
|
16
|
+
|
17
|
+
before(:all) do
|
18
|
+
@data = [
|
19
|
+
[ '2015-10-23T21:18:00+0200', '5ANJ1800.FIT' ],
|
20
|
+
[ '2015-10-11T15:06:59+0000', '5ABF0659.FIT' ],
|
21
|
+
[ '2014-04-19T07:31:06+0100', '44J63106.FIT' ],
|
22
|
+
[ '2015-05-07T16:16:50+0000', '557G1650.FIT' ]
|
23
|
+
]
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should convert a time stamps to a file names' do
|
27
|
+
@data.each do |t|
|
28
|
+
Fit4Ruby::FileNameCoder.encode(Time.parse(t[0])).should == t[1]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should convert file names to time stamps' do
|
33
|
+
@data.each do |t|
|
34
|
+
Fit4Ruby::FileNameCoder.decode(t[1]).should == Time.parse(t[0]).utc
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should fail to encode dates before 2010' do
|
39
|
+
lambda {
|
40
|
+
Fit4Ruby::FileNameCoder.encode(Time.parse('2009-12-31T00:00'))
|
41
|
+
}.should raise_error
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should fail to encode dates after 2033' do
|
45
|
+
lambda {
|
46
|
+
Fit4Ruby::FileNameCoder.encode(Time.parse('2034-01-01T00:00+00:00'))
|
47
|
+
}.should raise_error
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should fail to decode illegal file names' do
|
51
|
+
[ 'A.FIT', '0123ABCD', '5ZNJ1800.FIT',
|
52
|
+
'44063106.FIT', '44W63106.FIT',
|
53
|
+
'557O1650.FIT', '557G6050.FIT', '557G1660.FIT' ].each do |name|
|
54
|
+
lambda {
|
55
|
+
Fit4Ruby::FileNameCoder.decode(name)
|
56
|
+
}.should raise_error
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
data/spec/FitFile_spec.rb
CHANGED
@@ -15,6 +15,7 @@ require 'fit4ruby'
|
|
15
15
|
describe Fit4Ruby do
|
16
16
|
|
17
17
|
before(:each) do
|
18
|
+
ts = Time.now
|
18
19
|
a = Fit4Ruby::Activity.new
|
19
20
|
a.total_timer_time = 30 * 60.0
|
20
21
|
a.new_user_profile({ :age => 33, :height => 1.78, :weight => 73.0,
|
@@ -22,10 +23,24 @@ describe Fit4Ruby do
|
|
22
23
|
:max_hr => 178 })
|
23
24
|
|
24
25
|
a.new_event({ :event => 'timer', :event_type => 'start_time' })
|
25
|
-
a.new_device_info({ :
|
26
|
-
|
26
|
+
a.new_device_info({ :timestamp => ts,
|
27
|
+
:device_index => 0, :manufacturer => 'garmin',
|
28
|
+
:garmin_product => 'fenix3',
|
29
|
+
:serial_number => 123456789 })
|
30
|
+
a.new_device_info({ :timestamp => ts,
|
31
|
+
:device_index => 1, :manufacturer => 'garmin',
|
32
|
+
:garmin_product => 'fenix3_gps'})
|
33
|
+
a.new_device_info({ :timestamp => ts,
|
34
|
+
:device_index => 2, :manufacturer => 'garmin',
|
35
|
+
:garmin_product => 'hrm_run',
|
27
36
|
:battery_status => 'ok' })
|
28
|
-
|
37
|
+
a.new_device_info({ :timestamp => ts,
|
38
|
+
:device_index => 3, :manufacturer => 'garmin',
|
39
|
+
:garmin_product => 'sdm4',
|
40
|
+
:battery_status => 'ok' })
|
41
|
+
a.new_data_sources({ :timestamp => ts, :distance => 1,
|
42
|
+
:speed => 1, :cadence => 3, :elevation => 1,
|
43
|
+
:heart_rate => 2 })
|
29
44
|
laps = 0
|
30
45
|
0.upto(a.total_timer_time / 60) do |mins|
|
31
46
|
ts += 60
|
@@ -59,11 +74,22 @@ describe Fit4Ruby do
|
|
59
74
|
:event_type => 'marker', :vo2max => 52 })
|
60
75
|
a.new_event({ :timestamp => ts, :event => 'timer',
|
61
76
|
:event_type => 'stop_all' })
|
62
|
-
a.new_device_info({ :timestamp => ts, :device_index => 0,
|
63
|
-
:manufacturer => 'development' })
|
64
77
|
ts += 1
|
65
|
-
a.new_device_info({ :timestamp => ts,
|
66
|
-
:device_index =>
|
78
|
+
a.new_device_info({ :timestamp => ts,
|
79
|
+
:device_index => 0, :manufacturer => 'garmin',
|
80
|
+
:garmin_product => 'fenix3',
|
81
|
+
:serial_number => 123456789 })
|
82
|
+
a.new_device_info({ :timestamp => ts,
|
83
|
+
:device_index => 1, :manufacturer => 'garmin',
|
84
|
+
:garmin_product => 'fenix3_gps'})
|
85
|
+
a.new_device_info({ :timestamp => ts,
|
86
|
+
:device_index => 2, :manufacturer => 'garmin',
|
87
|
+
:garmin_product => 'hrm_run',
|
88
|
+
:battery_status => 'low' })
|
89
|
+
a.new_device_info({ :timestamp => ts,
|
90
|
+
:device_index => 3, :manufacturer => 'garmin',
|
91
|
+
:garmin_product => 'sdm4',
|
92
|
+
:battery_status => 'ok' })
|
67
93
|
ts += 120
|
68
94
|
a.new_event({ :timestamp => ts, :event => 'recovery_hr',
|
69
95
|
:event_type => 'marker', :recovery_hr => 132 })
|
@@ -79,7 +105,6 @@ describe Fit4Ruby do
|
|
79
105
|
File.delete(fit_file) if File.exists?(fit_file)
|
80
106
|
Fit4Ruby.write(fit_file, @activity)
|
81
107
|
File.exists?(fit_file).should be_true
|
82
|
-
puts File.absolute_path(fit_file)
|
83
108
|
|
84
109
|
b = Fit4Ruby.read(fit_file)
|
85
110
|
b.should == @activity
|
@@ -0,0 +1,34 @@
|
|
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, 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/GeoMath'
|
14
|
+
|
15
|
+
describe Fit4Ruby::GeoMath do
|
16
|
+
|
17
|
+
it 'should compute a distance between 2 points' do
|
18
|
+
p0_lat = 48.180506536737084
|
19
|
+
p0_lon = 11.611978523433208
|
20
|
+
points = [
|
21
|
+
# latitude, longitude, distance to p0 in meters
|
22
|
+
[ 48.180506536737084, 11.611978523433208, 0.0 ],
|
23
|
+
[ 48.18047543987632, 11.61195664666593, 3.821 ],
|
24
|
+
[ 48.18034409545362, 11.611852794885635, 20.339 ],
|
25
|
+
[ 48.17970883101225, 11.611351054161787, 100.225 ]
|
26
|
+
]
|
27
|
+
points.each do |p|
|
28
|
+
Fit4Ruby::GeoMath.distance(p0_lat, p0_lon,
|
29
|
+
p[0], p[1]).should be_within(0.001).of(p[2])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
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.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Schlaeger
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-10-
|
11
|
+
date: 2015-10-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bindata
|
@@ -84,10 +84,12 @@ files:
|
|
84
84
|
- lib/fit4ruby.rb
|
85
85
|
- lib/fit4ruby/Activity.rb
|
86
86
|
- lib/fit4ruby/Converters.rb
|
87
|
+
- lib/fit4ruby/DataSources.rb
|
87
88
|
- lib/fit4ruby/DeviceInfo.rb
|
88
89
|
- lib/fit4ruby/Event.rb
|
89
90
|
- lib/fit4ruby/FileCreator.rb
|
90
91
|
- lib/fit4ruby/FileId.rb
|
92
|
+
- lib/fit4ruby/FileNameCoder.rb
|
91
93
|
- lib/fit4ruby/FitDataRecord.rb
|
92
94
|
- lib/fit4ruby/FitDefinition.rb
|
93
95
|
- lib/fit4ruby/FitDefinitionField.rb
|
@@ -99,6 +101,7 @@ files:
|
|
99
101
|
- lib/fit4ruby/FitMessageRecord.rb
|
100
102
|
- lib/fit4ruby/FitRecord.rb
|
101
103
|
- lib/fit4ruby/FitRecordHeader.rb
|
104
|
+
- lib/fit4ruby/GeoMath.rb
|
102
105
|
- lib/fit4ruby/GlobalFitDictList.rb
|
103
106
|
- lib/fit4ruby/GlobalFitDictionaries.rb
|
104
107
|
- lib/fit4ruby/GlobalFitMessage.rb
|
@@ -115,7 +118,9 @@ files:
|
|
115
118
|
- lib/fit4ruby/Software.rb
|
116
119
|
- lib/fit4ruby/UserProfile.rb
|
117
120
|
- lib/fit4ruby/version.rb
|
121
|
+
- spec/FileNameCoder_spec.rb
|
118
122
|
- spec/FitFile_spec.rb
|
123
|
+
- spec/GeoMath_spec.rb
|
119
124
|
- tasks/changelog.rake
|
120
125
|
- tasks/gem.rake
|
121
126
|
- tasks/rdoc.rake
|
@@ -145,5 +150,7 @@ signing_key:
|
|
145
150
|
specification_version: 4
|
146
151
|
summary: Library to read GARMIN FIT files.
|
147
152
|
test_files:
|
153
|
+
- spec/FileNameCoder_spec.rb
|
148
154
|
- spec/FitFile_spec.rb
|
155
|
+
- spec/GeoMath_spec.rb
|
149
156
|
has_rdoc:
|