joule 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ class UnsupportedFileTypeException < Exception
2
+
3
+ def initialize
4
+ message = "Unsupported file type. Valid file types include SRM (.srm), PowerTap (.csv), iBike (.csv), and Garmin (.tcx)."
5
+ end
6
+
7
+ end
@@ -0,0 +1,9 @@
1
+ module Joule
2
+ module Hashable
3
+ def to_hash
4
+ hash = Hash.new
5
+ instance_variables.each { |v| hash[ v[1..-1].to_sym ] = instance_variable_get v }
6
+ hash
7
+ end
8
+ end
9
+ end
@@ -15,50 +15,50 @@ module Joule
15
15
  LONGITUDE = 13
16
16
  TIMESTAMP = 14
17
17
 
18
- def parse_header()
18
+ def parse_properties()
19
19
  records = FasterCSV.parse(@data)
20
20
  header = records.shift
21
21
 
22
- @properties = Joule::IBike::Properties.new
23
- @properties.version=header[1]
24
- @properties.units=header[2]
22
+ @workout.properties = Joule::IBike::Properties.new
23
+ @workout.properties.version=header[1]
24
+ @workout.properties.units=header[2]
25
25
  header = records.shift
26
- @properties.date_time = Time.mktime(header[0].to_i, header[1].to_i, header[2].to_i, header[3].to_i, header[4].to_i, header[5].to_i)
26
+ @workout.properties.date_time = Time.mktime(header[0].to_i, header[1].to_i, header[2].to_i, header[3].to_i, header[4].to_i, header[5].to_i)
27
27
  records.shift
28
28
  header = records.shift
29
- @properties.total_weight = header[0]
30
- @properties.energy = header[1]
31
- @properties.record_interval = header[4].to_i
32
- @properties.starting_elevation = header[5]
33
- @properties.total_climbing = header[6]
34
- @properties.wheel_size = header[7]
35
- @properties.temperature = header[8]
36
- @properties.starting_pressure = header[9]
37
- @properties.wind_scaling = header[10]
38
- @properties.riding_tilt = header[11]
39
- @properties.calibration_weight = header[12]
40
- @properties.cm = header[13]
41
- @properties.cda = header[14]
42
- @properties.crr = header[15]
29
+ @workout.properties.total_weight = header[0]
30
+ @workout.properties.energy = header[1]
31
+ @workout.properties.record_interval = header[4].to_i
32
+ @workout.properties.starting_elevation = header[5]
33
+ @workout.properties.total_climbing = header[6]
34
+ @workout.properties.wheel_size = header[7]
35
+ @workout.properties.temperature = header[8]
36
+ @workout.properties.starting_pressure = header[9]
37
+ @workout.properties.wind_scaling = header[10]
38
+ @workout.properties.riding_tilt = header[11]
39
+ @workout.properties.calibration_weight = header[12]
40
+ @workout.properties.cm = header[13]
41
+ @workout.properties.cda = header[14]
42
+ @workout.properties.crr = header[15]
43
43
  end
44
44
 
45
45
  def parse_data_points()
46
46
  records = FasterCSV.parse(@data).slice(5..-1)
47
47
  records.each_with_index { |record, index|
48
48
  data_point = DataPoint.new
49
- data_point.time = index * @properties.record_interval
49
+ data_point.time = index * @workout.properties.record_interval
50
50
  data_point.speed = convert_speed(record[SPEED].to_f)
51
51
  data_point.power = record[POWER].to_f
52
52
  data_point.distance = convert_distance(record[DISTANCE].to_f)
53
53
  data_point.cadence = record[CADENCE].to_i
54
54
  data_point.heartrate = record[HEARTRATE].to_i
55
- @data_points << data_point
55
+ @workout.data_points << data_point
56
56
  }
57
57
  end
58
58
 
59
59
  def parse_markers
60
60
  records = FasterCSV.parse(@data).slice(5..-1)
61
- @markers << create_workout_marker(records)
61
+ @workout.markers << create_workout_marker(records)
62
62
  end
63
63
 
64
64
  end
@@ -1,53 +1,53 @@
1
- class Marker
2
- attr_accessor :active
3
- attr_accessor :average_cadence
4
- attr_accessor :average_heartrate
5
- attr_accessor :average_power
6
- attr_accessor :average_power_to_weight
7
- attr_accessor :average_speed
8
- attr_accessor :comment
9
- attr_accessor :duration_seconds
10
- attr_accessor :distance
11
- attr_accessor :end
12
- attr_accessor :energy
13
- attr_accessor :intensity_factor
14
- attr_accessor :maximum_cadence
15
- attr_accessor :maximum_heartrate
16
- attr_accessor :maximum_power
17
- attr_accessor :maximum_power_to_weight
18
- attr_accessor :maximum_speed
19
- attr_accessor :normalized_power
20
- attr_accessor :start
21
- attr_accessor :start_time
22
- attr_accessor :training_stress_score
23
-
24
-
25
- def initialize(options = {})
26
- @active = true
27
- @average_cadence = 0
28
- @average_heartrate = 0
29
- @average_power = 0.0
30
- @average_power_to_weight = 0.0
31
- @average_speed = 0.0
32
- @comment = ""
33
- @duration_seconds = 0
34
- @distance = 0.0
35
- @end = options[:end]
36
- @energy = 0
37
- @intensity_factor = 0
38
- @maximum_cadence = 0
39
- @maximum_heartrate = 0
40
- @maximum_power = 0.0
41
- @maximum_power_to_weight = 0.0
42
- @maximum_speed = 0.0
43
- @normalized_power = 0
44
- @start = options[:start]
45
- @training_stress_score = 0.0
46
-
47
- end
48
-
49
- def start_time_in_seconds
50
- (@start_time.hour * 3600) + (@start_time.min * 60) + @start_time.sec
51
- end
1
+ module Joule
2
+ class Marker
3
+ include Joule::Hashable
4
+
5
+ attr_accessor :active
6
+ attr_accessor :average_cadence
7
+ attr_accessor :average_heartrate
8
+ attr_accessor :average_power
9
+ attr_accessor :average_power_to_weight
10
+ attr_accessor :average_speed
11
+ attr_accessor :comment
12
+ attr_accessor :duration_seconds
13
+ attr_accessor :distance
14
+ attr_accessor :end
15
+ attr_accessor :energy
16
+ attr_accessor :intensity_factor
17
+ attr_accessor :maximum_cadence
18
+ attr_accessor :maximum_heartrate
19
+ attr_accessor :maximum_power
20
+ attr_accessor :maximum_power_to_weight
21
+ attr_accessor :maximum_speed
22
+ attr_accessor :normalized_power
23
+ attr_accessor :start
24
+ attr_accessor :training_stress_score
25
+
26
+
27
+ def initialize(options = {})
28
+ @active = true
29
+ @average_cadence = 0
30
+ @average_heartrate = 0
31
+ @average_power = 0.0
32
+ @average_power_to_weight = 0.0
33
+ @average_speed = 0.0
34
+ @comment = ""
35
+ @duration_seconds = 0
36
+ @distance = 0.0
37
+ options[:end] ? @end = options[:end] : @end = 0
38
+ @energy = 0
39
+ @intensity_factor = 0
40
+ @maximum_cadence = 0
41
+ @maximum_heartrate = 0
42
+ @maximum_power = 0.0
43
+ @maximum_power_to_weight = 0.0
44
+ @maximum_speed = 0.0
45
+ @normalized_power = 0
46
+ options[:start] ? @start = options[:start] : @start = 0
47
+ @training_stress_score = 0.0
48
+ end
49
+
50
+ end
52
51
  end
53
52
 
53
+
@@ -0,0 +1,13 @@
1
+ module Joule
2
+ class PeakPower
3
+ include Joule::Hashable
4
+ attr_accessor :duration, :value, :start
5
+
6
+ def initialize(duration)
7
+ @duration = duration
8
+ @value = 0
9
+ @start = 0
10
+ end
11
+
12
+ end
13
+ end
@@ -10,16 +10,17 @@ module Joule
10
10
  HEARTRATE = 6
11
11
  MARKER = 7
12
12
 
13
- def parse_header()
13
+ def parse_properties()
14
14
  header = FasterCSV.parse(@data).shift
15
15
  records = FasterCSV.parse(@data)
16
- @properties = Joule::PowerTap::Properties.new
17
- @properties.speed_units = header[SPEED].to_s.downcase
18
- @properties.power_units = header[POWER].to_s.downcase
19
- @properties.distance_units = header[DISTANCE].to_s.downcase
16
+ @workout.properties = Joule::PowerTap::Properties.new
17
+ @workout.properties.speed_units = header[SPEED].to_s.downcase
18
+ @workout.properties.power_units = header[POWER].to_s.downcase
19
+ @workout.properties.distance_units = header[DISTANCE].to_s.downcase
20
20
  calculate_record_interval(records)
21
21
  end
22
-
22
+
23
+ private
23
24
  def parse_data_points()
24
25
  records = FasterCSV.parse(@data)
25
26
  records.shift
@@ -34,14 +35,14 @@ module Joule
34
35
  data_point.cadence = record[CADENCE].to_i
35
36
  data_point.heartrate = (record[HEARTRATE].to_i < 0) && 0 || record[HEARTRATE].to_i
36
37
 
37
- @data_points << data_point
38
+ @workout.data_points << data_point
38
39
  }
39
40
  end
40
41
 
41
42
  def parse_markers
42
43
  records = FasterCSV.parse(@data)
43
44
  records.shift
44
- @markers << create_workout_marker(records)
45
+ @workout.markers << create_workout_marker(records)
45
46
 
46
47
  current_marker_index = 0
47
48
 
@@ -57,23 +58,23 @@ module Joule
57
58
  end
58
59
 
59
60
  def create_marker(start)
60
- if(@markers.size.eql?(1))
61
- @markers << Marker.new(:start => 0)
61
+ if(@workout.markers.size.eql?(1))
62
+ @workout.markers << Marker.new(:start => 0)
62
63
  end
63
64
  set_previous_marker_end(start - 1)
64
- @markers << Marker.new(:start => start)
65
+ @workout.markers << Marker.new(:start => start)
65
66
  end
66
67
 
67
68
  def set_previous_marker_end(value)
68
- if(@markers.size > 1)
69
- @markers.last.end = value
69
+ if(@workout.markers.size > 1)
70
+ @workout.markers.last.end = value
70
71
  end
71
72
  end
72
73
 
73
74
  def calculate_record_interval(records)
74
75
  times = Array.new
75
76
  records[1..30].each_slice(2) {|s| times << ((s[1][MINUTES].to_f - s[0][MINUTES].to_f) * 60) }
76
- @properties.record_interval = times.average.round
77
+ @workout.properties.record_interval = times.average.round
77
78
  end
78
79
 
79
80
  end
@@ -1,2 +1,8 @@
1
1
  require 'joule/srm/parser'
2
- require 'joule/srm/properties'
2
+ require 'joule/srm/properties'
3
+
4
+ module Joule
5
+ module SRM
6
+ FILE_EXTENSION = "srm"
7
+ end
8
+ end
@@ -2,65 +2,44 @@ require 'kconv'
2
2
 
3
3
  module Joule
4
4
  module SRM
5
- class Parser
6
- include Joule::Calculator::MarkerCalculator
7
- include Joule::Calculator::PeakPowerCalculator
8
-
9
- SRM = '.srm'
5
+ class Parser < Joule::Base::Parser
10
6
  HEADER_SIZE=86
11
7
  MARKER_SIZE=270
12
8
  BLOCK_SIZE=6
13
9
 
14
- attr_reader :properties, :markers, :data_points, :peak_powers
15
-
16
- def initialize(data)
17
- @data = data
18
- @data_points = Array.new
19
- @markers = Array.new
20
- @peak_powers = Array.new
21
- end
22
-
23
- def parse(options = {})
24
- parse_header
10
+ def parse_workout()
25
11
  parse_markers
26
12
  parse_blocks
27
13
  parse_data_points
28
14
  parse_data_point_times
29
-
30
- if(options[:calculate_marker_values])
31
- calculate_marker_values()
32
- end
33
-
34
- if(options[:calculate_peak_power_values])
35
- calculate_peak_power_values(:durations => options[:durations], :total_duration => @markers.first.duration_seconds)
36
- end
37
15
  end
38
-
39
- def parse_header()
16
+
17
+ def parse_properties()
40
18
  str = @data.slice(0, HEADER_SIZE)
41
- @properties = Joule::SRM::Properties.new
42
- @properties.ident=str.slice(0,4)
43
- @properties.srm_date = str.slice(4,2).unpack('S')[0]
44
- @properties.wheel_size = str.slice(6,2).unpack('S')[0]
45
- @properties.record_interval_numerator = str.slice(8,1).unpack('C')[0]
46
- @properties.record_interval_denominator = str.slice(9,1).unpack('C')[0]
47
- @properties.block_count = str.slice(10,2).unpack('S')[0]
48
- @properties.marker_count = str.slice(12,2).unpack('S')[0]
49
- @properties.comment = str.slice(16,70).toutf8.strip
19
+ @workout.properties = Joule::SRM::Properties.new
20
+ @workout.properties.ident=str.slice(0,4)
21
+ @workout.properties.srm_date = str.slice(4,2).unpack('S')[0]
22
+ @workout.properties.wheel_size = str.slice(6,2).unpack('S')[0]
23
+ @workout.properties.record_interval_numerator = str.slice(8,1).unpack('C')[0]
24
+ @workout.properties.record_interval_denominator = str.slice(9,1).unpack('C')[0]
25
+ @workout.properties.block_count = str.slice(10,2).unpack('S')[0]
26
+ @workout.properties.marker_count = str.slice(12,2).unpack('S')[0]
27
+ @workout.properties.comment = str.slice(16,70).toutf8.strip
50
28
 
51
29
  str=@data.slice(HEADER_SIZE +
52
- (MARKER_SIZE * (@properties.marker_count + 1 )) +
53
- (BLOCK_SIZE * @properties.block_count) , 6)
30
+ (MARKER_SIZE * (@workout.properties.marker_count + 1 )) +
31
+ (BLOCK_SIZE * @workout.properties.block_count) , 6)
54
32
 
55
- @properties.zero_offset = str.slice(0,2).unpack('S')[0]
56
- @properties.slope = str.slice(2,2).unpack('S')[0]
57
- @properties.record_count = str.slice(4,2).unpack('S')[0]
33
+ @workout.properties.zero_offset = str.slice(0,2).unpack('S')[0]
34
+ @workout.properties.slope = str.slice(2,2).unpack('S')[0]
35
+ @workout.properties.record_count = str.slice(4,2).unpack('S')[0]
58
36
  end
59
37
 
38
+ private
60
39
  def parse_markers
61
40
  marker_offset = HEADER_SIZE
62
41
 
63
- (@properties.marker_count + 1).times { |i|
42
+ (@workout.properties.marker_count + 1).times { |i|
64
43
  str = @data.slice(marker_offset + (i * MARKER_SIZE), MARKER_SIZE)
65
44
 
66
45
  marker = Marker.new
@@ -68,16 +47,16 @@ module Joule
68
47
  marker.active = str.slice(255)
69
48
  marker.start = str.slice(256,2).unpack('S')[0] - 1
70
49
  marker.end = str.slice(258,2).unpack('S')[0] - 1
71
- @markers << marker
50
+ @workout.markers << marker
72
51
  }
73
52
 
74
53
  end
75
54
 
76
55
  def parse_blocks
77
- block_offset = HEADER_SIZE + (MARKER_SIZE * (@properties.marker_count + 1 ))
56
+ block_offset = HEADER_SIZE + (MARKER_SIZE * (@workout.properties.marker_count + 1 ))
78
57
 
79
58
  @blocks = Array.new
80
- @properties.block_count.times {|i|
59
+ @workout.properties.block_count.times {|i|
81
60
  str=@data.slice(block_offset + (i * BLOCK_SIZE), BLOCK_SIZE)
82
61
 
83
62
  block = Hash.new
@@ -89,26 +68,26 @@ module Joule
89
68
 
90
69
  def parse_data_points()
91
70
  count = 0
92
- start = HEADER_SIZE + (MARKER_SIZE * (@properties.marker_count + 1 )) + (BLOCK_SIZE * @properties.block_count) + 7
93
- total_distance = 0
71
+ start = HEADER_SIZE + (MARKER_SIZE * (@workout.properties.marker_count + 1 )) + (BLOCK_SIZE * @workout.properties.block_count) + 7
72
+ total_distance = 0.0
94
73
 
95
- while count < @properties.record_count
74
+ while count < @workout.properties.record_count
96
75
  record=@data.slice(start + (count * 5), 5)
97
76
  byte1=record.slice(0)
98
77
  byte2=record.slice(1)
99
78
  byte3=record.slice(2)
100
79
  data_point = DataPoint.new
101
80
 
102
- data_point.time = count * @properties.record_interval
81
+ data_point.time = count * @workout.properties.record_interval
103
82
  data_point.power = ( (byte2 & 0x0F) | (byte3 << 4) ).to_f
104
83
  data_point.speed = ( ( ( (byte2 & 0xF0) << 3) | (byte1 & 0x7F) ) * 32 ) #stored in mm/s
105
84
  data_point.cadence = record.slice(3)
106
85
  data_point.heartrate = record.slice(4)
107
86
 
108
- total_distance = total_distance + (data_point.speed * @properties.record_interval)
87
+ total_distance = total_distance + (data_point.speed * @workout.properties.record_interval)
109
88
  data_point.distance = total_distance #in mm
110
89
 
111
- @data_points << data_point
90
+ @workout.data_points << data_point
112
91
  count=count + 1
113
92
  end
114
93
  end
@@ -118,33 +97,15 @@ module Joule
118
97
  @blocks.each { |block|
119
98
  relative_count = 0
120
99
  while relative_count < block[:count]
121
- @data_points[count].time_of_day = block[:time]/100 + (@properties.record_interval*relative_count)
122
- @data_points[count].time_with_pauses = block[:time]/100 - @blocks[0][:time]/100 + (@properties.record_interval*(relative_count + 1))
100
+ @workout.data_points[count].time_of_day = block[:time]/100 + (@workout.properties.record_interval*relative_count)
101
+ @workout.data_points[count].time_with_pauses = block[:time]/100 - @blocks[0][:time]/100 + (@workout.properties.record_interval*(relative_count + 1))
123
102
 
124
103
  relative_count=relative_count+1
125
104
  count=count+1
126
105
  end
127
106
  }
128
107
 
129
- @properties.date_time = data_points.first.time_of_day.to_i
130
- end
131
-
132
- def calculate_marker_values
133
- @markers.each_with_index { |marker, i|
134
- calculate_marker_averages marker
135
- calculate_marker_maximums marker
136
- calculate_marker_training_metrics marker
137
-
138
- if i.eql?(0)
139
- marker.distance = @data_points.last.distance
140
- else
141
- marker.distance = @data_points[marker.end + 1].distance - @data_points[marker.start].distance
142
- end
143
-
144
- marker.duration_seconds = (marker.end - marker.start + 1) * @properties.record_interval
145
- marker.energy = (marker.average_power.round * marker.duration_seconds)/1000
146
-
147
- }
108
+ @workout.properties.date_time = @workout.data_points.first.time_of_day.to_i
148
109
  end
149
110
 
150
111
  end