fit4ruby 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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: