hrmparser 0.6.0

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.
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