hrmparser 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+
data/CHANGELOG.txt ADDED
@@ -0,0 +1,35 @@
1
+ 0.6.0 - August 15, 2009
2
+ Add support for Timex
3
+
4
+ 0.5.0 - July 25, 2009
5
+ Handle stop time in GPX
6
+
7
+ 0.4.9 - June 10, 2009
8
+ Support TCX with only LAT and LNG
9
+
10
+ 0.4.8 - May 23, 2009
11
+ Fixed importing when TCX LAP duration are totally completely incredibly insanely wrong
12
+
13
+ 0.4.6 - May 11th, 2009
14
+ Added support for Suunto full variables including speed, distance, etc.
15
+
16
+ 0.4.4 - May 10th, 2009
17
+ Fixed time-zone offset in parsing suunto data.
18
+
19
+ 0.4.2 - May 9th, 2009
20
+ Fixed GPX to grab time from first trackpoint.
21
+
22
+ 0.4.1 - May 9th, 2009
23
+ Added GPX support.
24
+ NOTE: all workouts do NOT account for stopped time.
25
+ duration is simply last time - first time. Will be fixed in future version.
26
+
27
+ 0.4.0 - May 8th, 2009
28
+ Added initial support for Suunto T6. Just grabs HRs for now.
29
+
30
+ 0.3.1 - May 6th, 2009
31
+ Handles missing data in a trackpoint now, including no lat and lng.
32
+
33
+ 0.2.3
34
+ Now should keep lat and lng set to nil when they don't exist in garmin files.
35
+
data/README.rdoc ADDED
@@ -0,0 +1,42 @@
1
+ = hrmparser
2
+
3
+ * http://github.com/teich/hrmparser/tree/master
4
+
5
+ == DESCRIPTION:
6
+
7
+ A ruby parser for polar and garmin hrm
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * Imports garmin 405. Generates a workout object with some averages calculated.
12
+ * Not the fastest right now.
13
+ * Very very early.
14
+
15
+ == REQUIREMENTS:
16
+
17
+ * hpricot
18
+
19
+ == LICENSE:
20
+
21
+ (The MIT License)
22
+
23
+ Copyright (c) 2009 Oren Teich
24
+
25
+ Permission is hereby granted, free of charge, to any person obtaining
26
+ a copy of this software and associated documentation files (the
27
+ 'Software'), to deal in the Software without restriction, including
28
+ without limitation the rights to use, copy, modify, merge, publish,
29
+ distribute, sublicense, and/or sell copies of the Software, and to
30
+ permit persons to whom the Software is furnished to do so, subject to
31
+ the following conditions:
32
+
33
+ The above copyright notice and this permission notice shall be
34
+ included in all copies or substantial portions of the Software.
35
+
36
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
37
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
38
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
39
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
40
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
41
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
42
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,64 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ #%w[rubygems rake rake/clean fileutils newgem rubigen].each { |f| require f }
5
+ #require File.dirname(__FILE__) + '/lib/hrmparser'
6
+
7
+ # Generate all the Rake tasks
8
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
9
+ # $hoe = Hoe.new('teich-hrmparser', HRMParser::VERSION::STRING) do |p|
10
+ # p.developer('Oren Teich', 'oren@teich.net')
11
+ # p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
12
+ # p.rubyforge_name = p.name # TODO this is default value
13
+ # # p.extra_deps = [
14
+ # # ['activesupport','>= 2.0.2'],
15
+ # # ]
16
+ # p.extra_dev_deps = [
17
+ # ['newgem', ">= #{::Newgem::VERSION}"]
18
+ # ]
19
+ #
20
+ # p.clean_globs |= %w[**/.DS_Store tmp *.log]
21
+ # path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
22
+ # p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
23
+ # p.rsync_args = '-av --delete --ignore-errors'
24
+ # end
25
+ #
26
+ # require 'newgem/tasks' # load /tasks/*.rake
27
+ # Dir['tasks/**/*.rake'].each { |t| load t }
28
+ #
29
+ # # TODO - want other tests/tasks run by default? Add them to the list
30
+ # task :default => [:spec, :features]
31
+ begin
32
+ require 'jeweler'
33
+ Jeweler::Tasks.new do |gemspec|
34
+ gemspec.name = "hrmparser"
35
+ gemspec.summary = "Heart Rate Monitor Parser"
36
+ gemspec.email = "oren@teich.net"
37
+ gemspec.homepage = "http://github.com/teich/hrmparser"
38
+ gemspec.description = "Parses Polar and Garmin HRM files."
39
+ gemspec.authors = ["Oren Teich"]
40
+
41
+ gemspec.files.exclude 'spec/samples/**/*'
42
+ gemspec.test_files.exclude 'spec/samples/**/*'
43
+
44
+ end
45
+ Jeweler::GemcutterTasks.new
46
+ rescue LoadError
47
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
48
+ end
49
+
50
+ # Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
51
+
52
+ # require 'spec/rake/spectask'
53
+ # Spec::Rake::SpecTask.new(:spec) do |spec|
54
+ # spec.libs << 'lib' << 'spec'
55
+ # spec.spec_files = FileList['spec/**/*_spec.rb']
56
+ # end
57
+ #
58
+ # Spec::Rake::SpecTask.new(:rcov) do |spec|
59
+ # spec.libs << 'lib' << 'spec'
60
+ # spec.pattern = 'spec/**/*_spec.rb'
61
+ # spec.rcov = true
62
+ # end
63
+
64
+ task :default => :spec
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 0
3
+ :major: 0
4
+ :minor: 6
data/hrmparser.gemspec ADDED
@@ -0,0 +1,61 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{hrmparser}
8
+ s.version = "0.6.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Oren Teich"]
12
+ s.date = %q{2009-12-13}
13
+ s.description = %q{Parses Polar and Garmin HRM files.}
14
+ s.email = %q{oren@teich.net}
15
+ s.extra_rdoc_files = [
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "CHANGELOG.txt",
21
+ "README.rdoc",
22
+ "Rakefile",
23
+ "VERSION.yml",
24
+ "hrmparser.gemspec",
25
+ "lib/hrmparser.rb",
26
+ "lib/hrmparser/arraymath.rb",
27
+ "lib/hrmparser/importer.rb",
28
+ "lib/hrmparser/importer/garmin.rb",
29
+ "lib/hrmparser/importer/gpx.rb",
30
+ "lib/hrmparser/importer/polar.rb",
31
+ "lib/hrmparser/importer/suunto.rb",
32
+ "lib/hrmparser/importer/timex.rb",
33
+ "lib/hrmparser/trackpoint.rb",
34
+ "lib/hrmparser/workout.rb",
35
+ "spec/arraymath_spec.rb",
36
+ "spec/hrmparser_spec.rb",
37
+ "spec/spec.opts",
38
+ "spec/spec_helper.rb"
39
+ ]
40
+ s.homepage = %q{http://github.com/teich/hrmparser}
41
+ s.rdoc_options = ["--charset=UTF-8"]
42
+ s.require_paths = ["lib"]
43
+ s.rubygems_version = %q{1.3.5}
44
+ s.summary = %q{Heart Rate Monitor Parser}
45
+ s.test_files = [
46
+ "spec/arraymath_spec.rb",
47
+ "spec/hrmparser_spec.rb",
48
+ "spec/spec_helper.rb"
49
+ ]
50
+
51
+ if s.respond_to? :specification_version then
52
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
53
+ s.specification_version = 3
54
+
55
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
56
+ else
57
+ end
58
+ else
59
+ end
60
+ end
61
+
@@ -0,0 +1,25 @@
1
+ require 'enumerator'
2
+
3
+ # Set of basic array math functions.
4
+ module ArrayMath
5
+
6
+ def aaverage
7
+ accum = self.asum
8
+ return nil if accum.nil? || self.size == 0
9
+ accum.to_f / self.size
10
+ end
11
+
12
+ def asum
13
+ self.map {|i| return nil if i.is_a?(String)}
14
+ inject(0){ |sum,item| sum + item }
15
+ end
16
+
17
+ ## Retun array FACTOR smaller. Average values to get smaller
18
+ def smoothed(factor)
19
+ self.enum_for(:each_slice, factor).map { |snipit| snipit.compact.aaverage }
20
+ end
21
+ end
22
+
23
+ class Array
24
+ include ArrayMath
25
+ end
@@ -0,0 +1,89 @@
1
+ module Importer
2
+ class Garmin
3
+ def initialize(opts = {:data => nil})
4
+ @data = opts[:data]
5
+ end
6
+
7
+ def restore
8
+ workout = HRMParser::Workout.new(:duration => 0)
9
+ #data = Importer.read_in_file(@file_name)
10
+
11
+ @xml = Hpricot::XML(@data)
12
+ workout.time = Time.parse((@xml/:Id).innerHTML)
13
+
14
+ # Grab the duration from the lap. This _can_ be totally, completly wrong - AKA 10 years long.
15
+ # So if we have trackpoints, we'll replace it down lower
16
+ (@xml/:Lap).each do |lap|
17
+ f_time = (lap/:TotalTimeSeconds).innerHTML
18
+ workout.duration += Float f_time
19
+ end
20
+
21
+ found = false
22
+ trackpoints = Array.new
23
+ distance_one = nil
24
+ time_one = nil
25
+
26
+ totaldistance = 0
27
+
28
+ (@xml/:Trackpoint).each do |t|
29
+ found = true
30
+ trackpoint = HRMParser::TrackPoint.new
31
+
32
+ trackpoint.time = Time.parse((t/:Time).innerHTML)
33
+
34
+ hr = (t/:HeartRateBpm/:Value).innerHTML
35
+ alt = (t/:AltitudeMeters).innerHTML
36
+ dis = (t/:DistanceMeters).innerHTML
37
+
38
+ trackpoint.hr = hr != "" ? hr.to_i : nil
39
+ trackpoint.altitude = alt != "" ? alt.to_f : nil
40
+ trackpoint.distance = dis != "" ? dis.to_f : nil
41
+
42
+ (t/:Position).each do |p|
43
+ trackpoint.lat = (p/:LatitudeDegrees).innerHTML.to_f
44
+ trackpoint.lng = (p/:LongitudeDegrees).innerHTML.to_f
45
+ end
46
+
47
+ if trackpoint.distance.nil? && !trackpoint.lat.nil?
48
+ totaldistance += trackpoint.calc_distance(trackpoints.last, trackpoint)
49
+ trackpoint.distance = totaldistance
50
+ end
51
+ trackpoint.speed = trackpoint.calc_speed(trackpoints.last, trackpoint)
52
+
53
+ trackpoints << trackpoint
54
+
55
+
56
+ ## CALCULATE SPEED. ICK.
57
+ # if distance_one.nil?
58
+ # distance_one = trackpoint.distance
59
+ # time_one = trackpoint.time
60
+ # else
61
+ # distance_two = trackpoint.distance
62
+ # next if distance_two.nil?
63
+ # time_two = trackpoint.time
64
+ # time_delta = time_two - time_one
65
+ # distance_delta = distance_two - distance_one
66
+ # if (distance_delta > 0 && time_delta > 0)
67
+ # trackpoint.speed = distance_delta / time_delta
68
+ # distance_one = distance_two
69
+ # time_one = time_two
70
+ # else
71
+ # trackpoint.speed = nil
72
+ # end
73
+ # end
74
+ end
75
+
76
+ if found
77
+ workout.duration = trackpoints.last.time - trackpoints.first.time
78
+ workout.trackpoints = trackpoints
79
+ workout.calc_average_speed!
80
+ workout.calc_altitude_gain!
81
+ workout.calc_average_hr!
82
+ workout.set_distance_from_trackpoints!
83
+ end
84
+
85
+ return workout
86
+ end
87
+
88
+ end
89
+ end
@@ -0,0 +1,51 @@
1
+ module Importer
2
+ class GPX
3
+ def initialize(opts = {:data => nil, :time_zone => "UTC"})
4
+ @data = opts[:data]
5
+ end
6
+
7
+ def restore
8
+ workout = HRMParser::Workout.new(:duration => 0)
9
+ @xml = Hpricot::XML(@data)
10
+
11
+ # Set the time based on first trackpoint. Seen an instance where the gpx begining time is wrong
12
+ ttime = (@xml/:trk/:trkpt/:time).first.innerHTML
13
+ workout.time = Time.parse(ttime)
14
+
15
+ trackpoints = []
16
+ distance = 0
17
+ workout.duration = 0
18
+ last_trackpoint = false
19
+ (@xml/:trk).each do |trk|
20
+ (trk/:trkpt).each do |trkpt|
21
+ trackpoint = HRMParser::TrackPoint.new
22
+ trackpoint.altitude = (trkpt/:ele).innerHTML.to_f
23
+ trackpoint.time = Time.parse((trkpt/:time).innerHTML)
24
+
25
+ trackpoint.lat = (trkpt.attributes)["lat"].to_f
26
+ trackpoint.lng = (trkpt.attributes)["lon"].to_f
27
+
28
+ distance += trackpoint.calc_distance(trackpoints.last, trackpoint)
29
+ trackpoint.distance = distance
30
+
31
+ trackpoint.speed = trackpoint.calc_speed(trackpoints.last, trackpoint)
32
+
33
+ if last_trackpoint && trackpoints.last && trackpoint.speed
34
+ workout.duration += trackpoint.time - last_trackpoint.time if trackpoint.speed > 0.04
35
+ end
36
+ trackpoints << trackpoint
37
+ last_trackpoint = trackpoint
38
+
39
+ end
40
+ end
41
+
42
+ # workout.duration = trackpoints.last.time - trackpoints.first.time
43
+ workout.trackpoints = trackpoints
44
+ workout.calc_average_speed!
45
+ workout.calc_altitude_gain!
46
+ workout.distance = trackpoints.last.distance
47
+ return workout
48
+ end
49
+ end
50
+
51
+ end
@@ -0,0 +1,103 @@
1
+ module Importer
2
+ class Polar
3
+
4
+ attr_reader :time_zone
5
+
6
+ def initialize(opts = {:data => nil, :time_zone => "UTC"})
7
+ @data = opts[:data]
8
+ @time_zone = opts[:time_zone]
9
+ end
10
+
11
+ def restore
12
+ workout = HRMParser::Workout.new(:duration => 0)
13
+
14
+ params = parse_params
15
+
16
+ date = params["Date"] + " " + params["StartTime"] + " " + @time_zone
17
+
18
+ length_array = params["Length"].split(/:/)
19
+ workout.duration = (length_array[0].to_f * 3600) + (length_array[1].to_f * 60) + (length_array[2].to_f)
20
+ workout.time = Time.parse(date)
21
+
22
+ workout.trackpoints = get_trackpoints(workout.time, params["Interval"].to_i)
23
+
24
+ workout.calc_average_hr!
25
+
26
+ return workout
27
+ end
28
+
29
+
30
+
31
+ private
32
+
33
+ # def parse_tabbed_blocks
34
+ # # This is the list of tabbed blocks
35
+ # tabbed_blocks = %w[IntTimes ExtraData Sumary-123 Summary-TH]
36
+ # tabbed_blocks.each do |block_name|
37
+ # @polarHash[block_name] = []
38
+ # block_text = find_block(block_name)
39
+ # block_text.each do |block_line|
40
+ # @polarHash[block_name] << block_line.split(/\t/)
41
+ # end
42
+ # end
43
+ # end
44
+
45
+ # Params is the only "ini" style block
46
+ def parse_params
47
+ hash = {}
48
+ param_block = find_block("Params")
49
+ param_block.each do |param|
50
+ # /=/ in case that doesn't work
51
+ key, value = param.split("=", 2)
52
+ key = key.strip unless key.nil?
53
+ value = value.strip unless value.nil?
54
+ hash[key] = value unless key.nil?
55
+ end
56
+ return hash
57
+ end
58
+
59
+ # Polar file has [Foo] blocks. Return the data in the block
60
+ def find_block(header)
61
+ found = false
62
+ block = []
63
+ @data.each do |line|
64
+ line.chomp!
65
+ found = false if line =~ /^\[.*\]$/
66
+ block << line if found
67
+ found = true if line =~ /\[#{header}\]/
68
+ end
69
+ return block
70
+ end
71
+
72
+ def parse_hrdata
73
+ hrdata = []
74
+ block_text = find_block("HRData")
75
+ block_text.each do |block_line|
76
+ hrdata << block_line.chomp
77
+ end
78
+ return hrdata
79
+ end
80
+
81
+ def get_trackpoints(start_time, interval)
82
+ trackpoints = []
83
+ rrcounter = 0
84
+
85
+ hrdata = parse_hrdata
86
+ hrdata.each do |hrd|
87
+ tp = HRMParser::TrackPoint.new
88
+
89
+ if (interval == 238)
90
+ rrcounter += hrd.to_i
91
+ tp.hr = 60000 / hrd.to_i
92
+ tp.time = start_time + (rrcounter/1000)
93
+ else
94
+ tp.hr = hrd.to_i
95
+ tp.time = start_time + (interval * trackpoints.size)
96
+ end
97
+
98
+ trackpoints << tp
99
+ end
100
+ return trackpoints
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,102 @@
1
+ module Importer
2
+ class Suunto
3
+ attr_reader :time_zone
4
+
5
+ def initialize(opts = {:data => nil, :time_zone => "UTC"})
6
+ @data = opts[:data]
7
+ @time_zone = opts[:time_zone]
8
+ end
9
+
10
+ def restore
11
+ workout = HRMParser::Workout.new(:duration => 0)
12
+
13
+ params = parse_params("HEADER")
14
+
15
+ # Using DateTime because 1.8 at leas doesn't have a Time.strptime
16
+ # And european ordeirng consfuses time.parse
17
+ # TODO: must be some better way
18
+ dt = DateTime.strptime(params["STARTTIME"], "%d.%m.%Y %H:%M.%S")
19
+ time_for_parse = dt.strftime("%b %d %H:%M:%S @time_zone %Y")
20
+
21
+ workout.time = Time.parse(time_for_parse)
22
+ workout.duration = params["DURATION"].to_f
23
+
24
+ workout.trackpoints = get_trackpoints
25
+
26
+ workout.calc_average_hr!
27
+ workout.calc_altitude_gain!
28
+ workout.calc_average_speed!
29
+ workout.set_distance_from_trackpoints!
30
+
31
+ return workout
32
+ end
33
+
34
+ private
35
+
36
+ def parse_params(string)
37
+ hash = {}
38
+ param_block = find_block(string)
39
+ param_block.each do |param|
40
+ # /=/ in case that doesn't work
41
+ key, value = param.split("=", 2)
42
+ key = key.strip unless key.nil?
43
+ value = value.strip unless value.nil?
44
+ hash[key] = value unless key.nil?
45
+ end
46
+ return hash
47
+ end
48
+
49
+ def find_block(header)
50
+ found = false
51
+ block = []
52
+ @data.each do |line|
53
+ line.chomp!
54
+ found = false if line =~ /^\[.*\]$/
55
+ block << line if found
56
+ found = true if line =~ /\[#{header}\]/
57
+ end
58
+ return block
59
+ end
60
+
61
+ def parse_data(string)
62
+ data = []
63
+ block_text = find_block(string)
64
+ block_text.each do |block_line|
65
+ data << block_line.chomp
66
+ end
67
+ return data
68
+ end
69
+
70
+ def get_trackpoints
71
+ trackpoints = []
72
+ logs = parse_data("POINTS")
73
+ for line in logs do
74
+ type, date, time, altitude, blank, blank, hr, epoc, respiration, ventilation, vo2, kcal, blank, blank, distance, speed, cadence, temp = line.split(/,/)
75
+ next if type == "\"T6LAP\""
76
+
77
+ trackpoint = HRMParser::TrackPoint.new
78
+
79
+ points_f = %w[epoc kcal speed]
80
+ points_i = %w[altitude hr respiration ventilation vo2 distance cadence temp]
81
+
82
+ points_f.each do |p|
83
+ value = (eval p).to_f
84
+ value = nil if value == 0.0
85
+ trackpoint.send("#{p}=".to_sym, value)
86
+ end
87
+
88
+ points_i.each do |p|
89
+ value = (eval p).to_i
90
+ value = nil if value == 0
91
+ trackpoint.send("#{p}=".to_sym, value)
92
+ end
93
+ dt = DateTime.strptime(date + " " + time, "%d.%m.%Y %H:%M.%S")
94
+ time_for_parse = dt.strftime("%b %d %H:%M:%S @time_zone %Y")
95
+ trackpoint.time = Time.parse(time_for_parse)
96
+
97
+ trackpoints << trackpoint
98
+ end
99
+ return trackpoints
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,118 @@
1
+ module Importer
2
+ class Timex
3
+ attr_reader :time_zone
4
+
5
+ def initialize(opts = {:data => nil, :time_zone => "UTC"})
6
+ @data = opts[:data]
7
+ @time_zone = opts[:time_zone]
8
+ end
9
+
10
+ def restore
11
+ workout = HRMParser::Workout.new(:duration => 0)
12
+
13
+ params = parse_params("session data")
14
+
15
+ dt = DateTime.strptime(params["Sessiondate"], "%d/%m/%Y %H:%M:%S")
16
+ time_for_parse = dt.strftime("%b %d %H:%M:%S @time_zone %Y")
17
+
18
+ workout.time = Time.parse(time_for_parse)
19
+ workout.duration = params["duration"].to_f
20
+
21
+ workout.trackpoints = get_trackpoints(workout.time)
22
+
23
+ workout.calc_average_hr!
24
+ workout.calc_altitude_gain!
25
+ workout.calc_average_speed!
26
+ workout.set_distance_from_trackpoints!
27
+
28
+ return workout
29
+
30
+ end
31
+
32
+
33
+ private
34
+
35
+ def parse_params(string)
36
+ hash = {}
37
+ param_block = find_block(string)
38
+ param_block.each do |param|
39
+ # /=/ in case that doesn't work
40
+ key, value = param.split("=", 2)
41
+ key = key.strip unless key.nil?
42
+ value = value.strip unless value.nil?
43
+ hash[key] = value unless key.nil?
44
+ end
45
+ return hash
46
+ end
47
+
48
+ def find_block(header)
49
+ found = false
50
+ block = []
51
+ @data.each do |line|
52
+ line.chomp!
53
+ found = false if line =~ /^\[.*\]$/
54
+ block << line if found
55
+ found = true if line =~ /\[#{header}\]/
56
+ end
57
+ return block
58
+ end
59
+
60
+ def parse_data(string)
61
+ data = []
62
+ block_text = find_block(string)
63
+ block_text.each do |block_line|
64
+ data << block_line.chomp
65
+ end
66
+ return data
67
+ end
68
+
69
+ def get_trackpoints(base_time)
70
+ trackpoints = []
71
+ have_gps = false
72
+ logs = parse_data("recorded")
73
+ fields = logs[0].split(/,/)
74
+ have_gps = true if fields.size > 5
75
+
76
+ for line in logs do
77
+ if have_gps
78
+ if line =~ /^".*"/ then line.gsub!(/"(.*?)"/,'\1') end # remove double-quotes at string beginning & end
79
+ seconds, hr, speed_imperial, distance_imperial, data_flag, lng, lat, altitude, acqs, trueh, magh = line.split(/,/)
80
+ else
81
+ seconds, hr, speed_imperial, distance_imperial, data_flag = line.split(/,/)
82
+ end
83
+
84
+
85
+ ## Convert speed and distance to metric - meters specifically
86
+ distance = distance_imperial.to_f * 1609.344
87
+ speed = speed_imperial.to_f * 0.44704
88
+
89
+
90
+ trackpoint = HRMParser::TrackPoint.new
91
+
92
+ points_f = %w[speed distance lat lng altitude]
93
+ points_i = %w[hr]
94
+
95
+
96
+ points_f.each do |p|
97
+ value = (eval p).to_f
98
+ value = nil if value == 0.0
99
+ trackpoint.send("#{p}=".to_sym, value)
100
+ end
101
+
102
+ points_i.each do |p|
103
+ value = (eval p).to_i
104
+ value = nil if value == 0
105
+ trackpoint.send("#{p}=".to_sym, value)
106
+ end
107
+
108
+ trackpoint.time = base_time + seconds.to_i
109
+
110
+ trackpoints << trackpoint
111
+ end
112
+ return trackpoints
113
+ end
114
+
115
+
116
+
117
+ end
118
+ end
@@ -0,0 +1,38 @@
1
+ require 'importer/garmin'
2
+ require 'importer/polar'
3
+ require 'importer/suunto'
4
+ require 'importer/gpx'
5
+ require 'importer/timex'
6
+
7
+ module Importer
8
+ def Importer.file_type(name)
9
+ case name
10
+ when /\.tcx$/i
11
+ return "GARMIN_XML"
12
+ when /\.hrm$/i
13
+ return "POLAR_HRM"
14
+ when /\.sdf$/i
15
+ return "SUUNTO"
16
+ when /\.gpx$/i
17
+ return "GPX"
18
+ when /\.csv$/i
19
+ f = File.new(name)
20
+ first_line = f.readline
21
+ f.close
22
+ if first_line.chomp == "[Timex Trainer Data File]"
23
+ return "TIMEX"
24
+ else
25
+ return "UNKNOWN CSV"
26
+ end
27
+ end
28
+ end
29
+
30
+ def Importer.read_in_file(name)
31
+ if File.readable?(name)
32
+ return open(name, "r")
33
+ else
34
+ puts "FILE ERROR, can't read #{name}"
35
+ end
36
+ end
37
+ end
38
+
@@ -0,0 +1,50 @@
1
+ module HRMParser
2
+ class TrackPoint
3
+
4
+ RAD_PER_DEG = 0.017453293 # PI/180
5
+
6
+ attr_accessor :lat, :lng, :altitude, :speed, :hr, :distance, :time, :cadence, :temp, :kcal, :epoc, :respiration, :ventilation, :vo2, :cadence
7
+
8
+ def initialize(opts = {:lat => nil, :lng => nil, :altitude => nil, :speed => nil, :hr => nil, :distance => nil, :cadence => nil, :time => Time.now})
9
+ @lat = opts[:lat]
10
+ @lng = opts[:lng]
11
+ @altitude = opts[:altitude]
12
+ @speed = opts[:speed]
13
+ @hr = opts[:hr]
14
+ @distance = opts[:distance]
15
+ @time = opts[:time]
16
+ @cadence = opts[:cadence]
17
+ end
18
+
19
+ def calc_distance(pointA, pointB)
20
+ return 0 if pointA.nil? || pointA.lat.nil? || pointB.nil? || pointB.lat.nil?
21
+
22
+ dlng = pointB.lng - pointA.lng
23
+ dlat = pointB.lat - pointA.lat
24
+
25
+ dlat_rad = dlat * RAD_PER_DEG
26
+ dlng_rad = dlng * RAD_PER_DEG
27
+
28
+ lat1_rad = pointA.lat * RAD_PER_DEG
29
+ lng1_rad = pointA.lng * RAD_PER_DEG
30
+
31
+ lat2_rad = pointB.lat * RAD_PER_DEG
32
+ lng2_rad = pointB.lng * RAD_PER_DEG
33
+
34
+ a = (Math.sin(dlat_rad/2))**2 + Math.cos(lat1_rad) * Math.cos(lat2_rad) * (Math.sin(dlng_rad/2))**2
35
+ c = 2 * Math.atan2( Math.sqrt(a), Math.sqrt(1-a))
36
+
37
+ return 6371000 * c
38
+ end
39
+
40
+ def calc_speed(pointA, pointB)
41
+ return nil if pointA.nil? || pointA.lat.nil? || pointB.nil? || pointB.lat.nil?
42
+ time_delta = pointB.time - pointA.time
43
+ return nil if time_delta == 0
44
+ distance_delta = pointB.distance - pointA.distance
45
+ return distance_delta / time_delta
46
+ end
47
+ end
48
+ end
49
+
50
+
@@ -0,0 +1,66 @@
1
+ module HRMParser
2
+ class Workout
3
+ attr_accessor :duration, :distance, :time, :name, :file_name, :trackpoints
4
+ attr_reader :average_hr, :data, :average_speed, :altitude_gain
5
+
6
+ def initialize(opts = {:duration => nil, :distance => nil, :time => Time.now, :name => nil, :file_name => nil})
7
+ @duration = opts[:duration]
8
+ @name = opts[:name]
9
+ @time = opts[:time]
10
+ @distance = opts[:distance]
11
+ @file_name = opts[:file_name]
12
+
13
+ @data = nil
14
+ @trackpoints = {}
15
+ end
16
+
17
+
18
+ def calc_average_hr!
19
+ ahr = heart_rates.compact.aaverage
20
+ @average_hr = ahr == 0.0 ? nil : ahr
21
+ end
22
+
23
+ def calc_average_speed!
24
+ aspeed = speeds.compact.aaverage
25
+ @average_speed = aspeed == 0.0 ? nil : aspeed
26
+ end
27
+
28
+ def calc_altitude_gain!
29
+ gain = 0
30
+ smoothed_altitude = altitudes.compact.smoothed(10)
31
+ start = smoothed_altitude.first
32
+ smoothed_altitude.each do |alt|
33
+ diff = alt - start
34
+ if (diff > 0.5)
35
+ gain += diff
36
+ end
37
+ start = alt
38
+ end
39
+ @altitude_gain = gain == 0.0 ? nil : gain
40
+
41
+ end
42
+
43
+
44
+ ## Some helper functions that return specific files from trackpoint as array
45
+ def heart_rates
46
+ @trackpoints.map {|tp| tp.hr }
47
+ end
48
+
49
+ def speeds
50
+ @trackpoints.map {|tp| tp.speed }
51
+ end
52
+
53
+ def altitudes
54
+ @trackpoints.map { |tp| tp.altitude }
55
+ end
56
+
57
+ def set_distance_from_trackpoints!
58
+ @trackpoints.reverse_each do |tp|
59
+ if !tp.distance.nil?
60
+ self.distance = tp.distance
61
+ break
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
data/lib/hrmparser.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'rubygems'
2
+ require 'hpricot'
3
+ require 'time'
4
+
5
+ module HRMParser; end
6
+
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/hrmparser')
8
+
9
+ require 'arraymath'
10
+ require 'trackpoint'
11
+ require 'workout'
12
+ require 'importer'
@@ -0,0 +1,26 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ module ArrayMath
4
+ describe "ArrayMath" do
5
+ context "Sum" do
6
+ it "sums an array" do
7
+ array = [1,2,3,4,5]
8
+ array.asum.should == 15
9
+ end
10
+ it "can not sum an array of strings" do
11
+ array = ["hello", "goodbye"]
12
+ array.asum.should == nil
13
+ end
14
+ end
15
+ context "average" do
16
+ it "averages an array of integers and returns a float" do
17
+ array = [2,3,4,3,2]
18
+ array.aaverage.should == 2.8
19
+ end
20
+ it "averages an array of floats" do
21
+ array = [1.5,2.5,3.5,2.5,1.5]
22
+ array.aaverage.should == 2.3
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,298 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ # Time to add your specs!
4
+ # http://rspec.info/
5
+ module HRMParser
6
+ describe "TrackPoint" do
7
+ context "new trackpoint" do
8
+ it "has no variables set" do
9
+ hrm = TrackPoint.new
10
+ hrm.speed == nil
11
+ hrm.distance == nil
12
+ hrm.lat.should == nil
13
+ hrm.lng.should == nil
14
+ hrm.altitude.should == nil
15
+ hrm.hr.should == nil
16
+ end
17
+ end
18
+ end
19
+
20
+ describe "Workout" do
21
+ context "new workout" do
22
+ it "has no variables set" do
23
+ workout = Workout.new
24
+ workout.distance.should == nil
25
+ workout.duration.should == nil
26
+ workout.average_hr.should == nil
27
+ workout.name.should == nil
28
+ workout.file_name.should == nil
29
+ end
30
+ it "set name through initializer" do
31
+ workout = Workout.new(:name => "test workout")
32
+ workout.name.should == "test workout"
33
+ end
34
+ it "can not set average_hr during init" do
35
+ workout = Workout.new(:average_hr => 150)
36
+ workout.average_hr.should == nil
37
+ end
38
+ end
39
+
40
+ context "Identifies files" do
41
+ it "identify file as garmin" do
42
+ type = Importer.file_type("spec/samples/small-garmin.TCX")
43
+ type.should == "GARMIN_XML"
44
+ end
45
+ it "identification returns nil if no file specified" do
46
+ type = Importer.file_type("")
47
+ type.should == nil
48
+ end
49
+ it "identify file as polar" do
50
+ type = Importer.file_type("spec/samples/polarRS400.hrm")
51
+ type.should == "POLAR_HRM"
52
+ end
53
+ it "identifies timex" do
54
+ type = Importer.file_type("spec/samples/timex/HR.csv")
55
+ type.should == "TIMEX"
56
+ end
57
+ end
58
+
59
+ # context "Parse a GPS file" do
60
+ # it "finds the duration, time" do
61
+ # filename = "spec/samples/gps-with-suunto.gpx"
62
+ # data = File.read(filename)
63
+ # importer = Importer::GPX.new(:data => data)
64
+ # workout = importer.restore
65
+ # workout.time.should == Time.parse("Thu May 07 21:32:31 UTC 2009")
66
+ #
67
+ # # Duration is actualy less, but we don't account for stopped time right now
68
+ # workout.duration.should be_close(6284,1)
69
+ # end
70
+ # it "calculates the distance and speed" do
71
+ # filename = "spec/samples/gps-with-suunto.gpx"
72
+ # data = File.read(filename)
73
+ # importer = Importer::GPX.new(:data => data)
74
+ # workout = importer.restore
75
+ # workout.average_speed.should be_close(6.7, 0.2)
76
+ # workout.distance.should be_close(26427, 1)
77
+ # end
78
+ # it "handles files with drops" do
79
+ # filename = "spec/samples/gps-flat-run.gpx"
80
+ # data = File.read(filename)
81
+ # importer = Importer::GPX.new(:data => data)
82
+ # workout = importer.restore
83
+ # workout.average_speed.should be_close(2.9, 0.2)
84
+ # workout.distance.should be_close(11453, 1)
85
+ # workout.altitude_gain.should be_close(325, 10)
86
+ # end
87
+ # it "deals with stopping and calculates duration correctly" do
88
+ # filename = "spec/samples/gps-with-stops.gpx"
89
+ # data = File.read(filename)
90
+ # importer = Importer::GPX.new(:data => data)
91
+ # workout = importer.restore
92
+ # workout.average_speed.should be_close(6.5, 0.2)
93
+ # workout.distance.should be_close(5230, 1)
94
+ # workout.altitude_gain.should be_close(11, 10)
95
+ # workout.duration.should be_close(1149, 1)
96
+ # end
97
+ #
98
+ # it "deals with stopping and calculates duration correctly" do
99
+ # filename = "spec/samples/gps-with-stops-2.gpx"
100
+ # data = File.read(filename)
101
+ # importer = Importer::GPX.new(:data => data)
102
+ # workout = importer.restore
103
+ # workout.average_speed.should be_close(5.7, 0.2)
104
+ # workout.distance.should be_close(3099, 1)
105
+ # workout.altitude_gain.should be_close(24, 10)
106
+ # workout.duration.should be_close(564, 1)
107
+ # end
108
+ #
109
+ # end
110
+ #
111
+ # context "Parse garmin file" do
112
+ # it "finds workout start time on a short workout" do
113
+ # filename = "spec/samples/indoor-garmin-405.TCX"
114
+ # data = File.read(filename)
115
+ # importer = Importer::Garmin.new(:data => data)
116
+ # workout = importer.restore
117
+ # workout.time.should == Time.parse("Fri Aug 22 01:04:55 UTC 2008")
118
+ # end
119
+ # it "finds the duration on a short workout" do
120
+ # filename = "spec/samples/indoor-garmin-405.TCX"
121
+ # data = File.read(filename)
122
+ # importer = Importer::Garmin.new(:data => data)
123
+ # workout = importer.restore
124
+ # workout.duration.should be_close(755, 1)
125
+ # end
126
+ # it "indoor workout has no trackpoints" do
127
+ # filename = "spec/samples/indoor-garmin-405.TCX"
128
+ # data = File.read(filename)
129
+ # importer = Importer::Garmin.new(:data => data)
130
+ # workout = importer.restore
131
+ # workout.distance.should be_nil
132
+ # workout.average_hr.should be_nil
133
+ # workout.average_speed.should be_nil
134
+ # workout.altitude_gain.should be_nil
135
+ # workout.trackpoints.should == {}
136
+ # end
137
+ # it "parses files with only LAT and LNG" do
138
+ # filename = "spec/samples/garmin-only-lat-lng.tcx"
139
+ # data = File.read(filename)
140
+ # importer = Importer::Garmin.new(:data => data)
141
+ # workout = importer.restore
142
+ # workout.distance.should be_close(172052, 1)
143
+ # workout.average_hr.should be_nil
144
+ # workout.average_speed.should be_close(5.93, 0.1)
145
+ # workout.altitude_gain.should be_close(372, 10)
146
+ # end
147
+ #
148
+ # # Parsing the full XML is just slow. Commenting out for now.
149
+ # it "gets workout level settings for outdoor workout" do
150
+ # filename = "spec/samples/outdoor-garmin-405.TCX"
151
+ # data = File.read(filename)
152
+ # importer = Importer::Garmin.new(:data => data)
153
+ # workout = importer.restore
154
+ # workout.distance.should be_close(11740, 5)
155
+ # workout.average_hr.should be_close(149.7, 0.5)
156
+ # workout.average_speed.should be_close(1.5, 0.2)
157
+ # workout.altitude_gain.should be_close(580, 25)
158
+ # end
159
+ #
160
+ # it "gets workout level settings for weird distance workout" do
161
+ # filename = "spec/samples/garmin-405-dies-distance.TCX"
162
+ # data = File.read(filename)
163
+ # importer = Importer::Garmin.new(:data => data)
164
+ # workout = importer.restore
165
+ # workout.distance.should be_close(9426, 1)
166
+ # workout.average_hr.should == nil
167
+ # workout.average_speed.should be_close(6.7, 0.2)
168
+ # workout.altitude_gain.should be_close(40, 10)
169
+ # end
170
+ #
171
+ # it "doesn't have any 0 in latitude" do
172
+ # filename = "spec/samples/garmin-405-with-0-0.TCX"
173
+ # data = File.read(filename)
174
+ # importer = Importer::Garmin.new(:data => data)
175
+ # workout = importer.restore
176
+ # workout.trackpoints.map {|tp| tp.lat.should_not == 0.0}
177
+ # workout.trackpoints.map {|tp| tp.lat.should_not == "undefined"}
178
+ # end
179
+ #
180
+ # it "handles files with INSANE duration" do
181
+ # filename = "spec/samples/insane-duration.TCX"
182
+ # data = File.read(filename)
183
+ # importer = Importer::Garmin.new(:data => data)
184
+ # workout = importer.restore
185
+ # workout.duration.should be_close(4996, 0.2)
186
+ # end
187
+ # end
188
+ #
189
+ # context "Parse polar RS400 file" do
190
+ # it "finds the duration and time" do
191
+ # filename ="spec/samples/polarRS400.hrm"
192
+ # data = File.read(filename)
193
+ # importer = Importer::Polar.new(:data => data, :time_zone => "Pacific Time (US & Canada)")
194
+ # workout = importer.restore
195
+ # workout.duration.should be_close(3569,1)
196
+ # workout.time.should == Time.parse("Thu Apr 16 12:01:55 -0700 2009")
197
+ # end
198
+ # it "calculates the average heartrate" do
199
+ # filename ="spec/samples/polarRS400.hrm"
200
+ # data = File.read(filename)
201
+ # importer = Importer::Polar.new(:data => data, :time_zone => "Pacific Time (US & Canada)")
202
+ # workout = importer.restore
203
+ # workout.average_hr.should be_close(145, 1)
204
+ # end
205
+ # end
206
+ #
207
+ # context "Parse a Polar RR file" do
208
+ # it "calculates the heart rate from RR" do
209
+ # filename ="spec/samples/polarRS800-RR.hrm"
210
+ # data = File.read(filename)
211
+ # importer = Importer::Polar.new(:data => data, :time_zone => "Pacific Time (US & Canada)")
212
+ # workout = importer.restore
213
+ # workout.trackpoints.each {|tp| tp.hr.should < 220 && tp.hr.should > 30}
214
+ # workout.average_hr.should be_close(115, 1)
215
+ # workout.average_speed.should == nil
216
+ # end
217
+ # end
218
+ #
219
+ # context "Parse a Suunto T6C RR file" do
220
+ # it "finds the duration and time" do
221
+ # filename = "spec/samples/suunto-t6-RR-stops.sdf"
222
+ # data = File.read(filename)
223
+ # importer = Importer::Suunto.new(:data => data, :time_zone => "Pacific Time (US & Canada)")
224
+ # workout = importer.restore
225
+ # workout.duration.should be_close(4781,1)
226
+ # workout.time.should == Time.parse("Thu May 07 14:16:07 -0700 2009")
227
+ # end
228
+ # it "calculates the average HR & altitude" do
229
+ # filename = "spec/samples/suunto-t6-RR-stops.sdf"
230
+ # data = File.read(filename)
231
+ # importer = Importer::Suunto.new(:data => data, :time_zone => "Pacific Time (US & Canada)")
232
+ # workout = importer.restore
233
+ # workout.average_hr.should be_close(152,1)
234
+ # workout.average_speed.should == nil
235
+ # workout.trackpoints.each { |tp| tp.speed.should == nil }
236
+ # workout.altitude_gain.should be_close(115, 10)
237
+ # end
238
+ # it "calculates the speed and distance" do
239
+ # filename = "spec/samples/suunto-with-cadence-speed-distance.sdf"
240
+ # data = File.read(filename)
241
+ # importer = Importer::Suunto.new(:data => data, :time_zone => "Pacific Time (US & Canada)")
242
+ # workout = importer.restore
243
+ # workout.average_hr.should be_close(131,1)
244
+ # workout.average_speed.should be_close(9.3, 0.1)
245
+ # workout.altitude_gain.should be_close(75, 15)
246
+ # workout.distance.should == 124597
247
+ # end
248
+ # end
249
+
250
+ context "Parse Timex CSV" do
251
+ it "handles HR only" do
252
+ filename = "spec/samples/timex/HR.csv"
253
+ data = File.read(filename)
254
+ importer = Importer::Timex.new(:data => data, :time_zone => "Pacific Time (US & Canada)")
255
+ workout = importer.restore
256
+ workout.duration.should be_close(3298, 1)
257
+ workout.time.should == Time.parse("Jan 01 09:27:26 -0800 2009")
258
+ workout.average_hr.should be_close(81,1)
259
+ end
260
+
261
+ it "does speed and distance" do
262
+ filename = "spec/samples/timex/Speed+Distance.csv"
263
+ data = File.read(filename)
264
+ importer = Importer::Timex.new(:data => data, :time_zone => "Pacific Time (US & Canada)")
265
+ workout = importer.restore
266
+ workout.duration.should be_close(2580, 1)
267
+ workout.time.should == Time.parse("Feb 01 08:27:02 -0800 2009")
268
+ workout.average_hr.should == nil
269
+ workout.distance.should be_close(8391, 1)
270
+ workout.average_speed.should be_close(3.2, 0.2)
271
+ end
272
+
273
+ it "handles a GPS only file" do
274
+ filename = "spec/samples/timex/GPS.csv"
275
+ data = File.read(filename)
276
+ importer = Importer::Timex.new(:data => data, :time_zone => "Pacific Time (US & Canada)")
277
+ workout = importer.restore
278
+ workout.duration.should be_close(1937, 1)
279
+ workout.time.should == Time.parse("Jun 01 12:03:12 -0700 2009")
280
+ workout.average_hr.should == nil
281
+ workout.distance.should be_close(6035, 1)
282
+ workout.average_speed.should be_close(3.05, 0.05)
283
+ end
284
+
285
+ it "handles GPS & HR" do
286
+ filename = "spec/samples/timex/GPS+HR.csv"
287
+ data = File.read(filename)
288
+ importer = Importer::Timex.new(:data => data, :time_zone => "Pacific Time (US & Canada)")
289
+ workout = importer.restore
290
+ workout.duration.should be_close(2677.0, 1)
291
+ workout.time.should == Time.parse("Jul 01 12:00:43 -0700 2009")
292
+ workout.average_hr.should be_close(169,1)
293
+ workout.distance.should be_close(6435, 1)
294
+ workout.average_speed.should be_close(2.34, 0.05)
295
+ end
296
+ end
297
+ end
298
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --colour
@@ -0,0 +1,9 @@
1
+ dir = File.dirname(__FILE__)
2
+ lib_path = File.expand_path("#{dir}/../lib")
3
+ $LOAD_PATH.unshift lib_path unless $LOAD_PATH.include?(lib_path)
4
+ $_spec_spec = true # Prevents Kernel.exit in various places
5
+
6
+ require 'spec'
7
+
8
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
9
+ require 'hrmparser'
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hrmparser
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.0
5
+ platform: ruby
6
+ authors:
7
+ - Oren Teich
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-13 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Parses Polar and Garmin HRM files.
17
+ email: oren@teich.net
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - .gitignore
26
+ - CHANGELOG.txt
27
+ - README.rdoc
28
+ - Rakefile
29
+ - VERSION.yml
30
+ - hrmparser.gemspec
31
+ - lib/hrmparser.rb
32
+ - lib/hrmparser/arraymath.rb
33
+ - lib/hrmparser/importer.rb
34
+ - lib/hrmparser/importer/garmin.rb
35
+ - lib/hrmparser/importer/gpx.rb
36
+ - lib/hrmparser/importer/polar.rb
37
+ - lib/hrmparser/importer/suunto.rb
38
+ - lib/hrmparser/importer/timex.rb
39
+ - lib/hrmparser/trackpoint.rb
40
+ - lib/hrmparser/workout.rb
41
+ - spec/arraymath_spec.rb
42
+ - spec/hrmparser_spec.rb
43
+ - spec/spec.opts
44
+ - spec/spec_helper.rb
45
+ has_rdoc: true
46
+ homepage: http://github.com/teich/hrmparser
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options:
51
+ - --charset=UTF-8
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ requirements: []
67
+
68
+ rubyforge_project:
69
+ rubygems_version: 1.3.5
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: Heart Rate Monitor Parser
73
+ test_files:
74
+ - spec/arraymath_spec.rb
75
+ - spec/hrmparser_spec.rb
76
+ - spec/spec_helper.rb