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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 012c8c74873a9ce1d703e72c75e14019d480db0f
4
- data.tar.gz: 32137198f2a8a87dc473c7bdc0c3bdc978a9e31c
3
+ metadata.gz: 7f651054b5f16738a2ca5fc4146cdb817c4ef102
4
+ data.tar.gz: b1af399d6335bcf932dfdeb3b35897191bfc6290
5
5
  SHA512:
6
- metadata.gz: b5c6653686b550a201b71ad021af82d79cc03b092fceda5441fd286c0a3e34ff2c7f2291b35aa5f8ed3c740060953a47b63ef1c2c895a8571cf09a0464566640
7
- data.tar.gz: bb3b5778604e1f342163fd751c3e3beccb09c3aa8ddcebb5deb7274c1623e6d194408b54486f34837d80645aa471e5c2132236551d5bf4c8ff2fc1f3864f3a07
6
+ metadata.gz: 773e7c729a6ecae371b03269da9da1312f9ca7da35f3e14e6cc6e2c8429955d5f4ea0f965a975bde69368853d9a7555280f4da9a630dd8e1525f827e090b06d1
7
+ data.tar.gz: 2e4ea74d55a4496977133a15e0ccb5bf0902b4104c32587e783c3427c175cc123c70a498a8e0b0a56cc28451e3fb95b6c0a509b9b28431ff376194b2a94c0932
@@ -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, :user_profiles,
32
- :sessions, :laps, :records, :events, :personal_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.error "Activity has no valid timestamp"
67
+ Log.fatal "Activity has no valid timestamp"
65
68
  end
66
69
  unless @total_timer_time
67
- Log.error "Activity has no valid total_timer_time"
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.error "Activity record requires #{@num_sessions}, but "
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
- unless lap.message_index == index
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 + @user_profiles + @events + @sessions + @laps +
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 && @user_profiles == a.user_profiles &&
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(field_values)
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
+
@@ -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
@@ -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 <=> fdr.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)
@@ -41,7 +41,7 @@ module Fit4Ruby
41
41
 
42
42
  def check
43
43
  if architecture.snapshot > 1
44
- Log.error "Illegal architecture value #{architecture.snapshot}"
44
+ Log.fatal "Illegal architecture value #{architecture.snapshot}"
45
45
  end
46
46
  fields.each { |f| f.check }
47
47
  end
@@ -33,7 +33,7 @@ module Fit4Ruby
33
33
  begin
34
34
  io = ::File.open(file_name, 'rb')
35
35
  rescue StandardError => e
36
- Log.critical("Cannot open FIT file '#{file_name}'", e)
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.critical("Cannot open FIT file '#{file_name}'", e)
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.critical "Checksum error in file '#{@file_name}'. " +
108
- "Computed #{"%04X" % crc} instead of #{"%04X" % crc_ref}."
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
+
@@ -67,7 +67,7 @@ module Fit4Ruby
67
67
  entry 5, 'critical'
68
68
 
69
69
  dict 'device_type'
70
- entry 0, 'position' # Just a guess
70
+ entry 0, 'gps' # Just a guess
71
71
  entry 3, 'acceleration' # Just a guess
72
72
  entry 4, 'barometric_pressure' # Just a guess
73
73
  entry 1, 'antfs'
@@ -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 these are all guesses right now.
259
- message 22, 'metering_devices'
260
- field 0, 'uint8', 'speed'
261
- field 1, 'uint8', 'distance'
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', 'altitude'
263
+ field 3, 'uint8', 'elevation'
264
264
  field 4, 'uint8', 'heart_rate'
265
- field 5, 'enum', 'undocumented_field_5' # 0 or 3 seen
266
- field 6, 'uint8', 'undocumented_field_6' # First found in FR920XT
267
- field 14, 'uint8', 'undocumented_field_14' # First found in FR920XT
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.error "Record has no timestamp" unless r.timestamp
53
+ Log.fatal "Record has no timestamp" unless r.timestamp
51
54
  if r.timestamp < ts
52
- Log.error "Record has earlier timestamp than previous record"
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.error "Record has smaller distance than previous record"
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
- class ILogger < Logger
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
- def fatal(msg)
24
- super
25
- exit 1
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
- def critical(msg, exception = nil)
29
- if exception
30
- raise Error, "#{msg}: #{exception.message}", exception.backtrace
31
- else
32
- raise Error, msg
33
- end
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.new(STDOUT)
52
+ Log = ILogger.instance
39
53
  Log.level = Logger::WARN
40
54
  Log.formatter = proc do |severity, time, progname, msg|
41
55
  msg + "\n"
@@ -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.error 'first_lap_index is not set'
57
+ Log.fatal 'first_lap_index is not set'
58
58
  end
59
59
  unless @num_laps
60
- Log.error 'num_laps is not set'
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.error "Session references lap #{i} which is not contained in "
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.
@@ -1,4 +1,4 @@
1
1
  module Fit4Ruby
2
2
  # The version number of the library.
3
- VERSION = '0.0.7'
3
+ VERSION = '0.0.8'
4
4
  end
@@ -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({ :device_index => 0, :manufacturer => 'development' })
26
- a.new_device_info({ :device_index => 1, :manufacturer => 'development',
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
- ts = Time.now
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, :manufacturer => 'development',
66
- :device_index => 1, :battery_status => 'low' })
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.7
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-12 00:00:00.000000000 Z
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: