postrunner 0.10.1 → 0.11.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.
- checksums.yaml +4 -4
- data/lib/postrunner/Activity.rb +2 -1
- data/lib/postrunner/ActivitySummary.rb +92 -0
- data/lib/postrunner/ChartView.rb +5 -5
- data/lib/postrunner/DeviceList.rb +1 -1
- data/lib/postrunner/EPO_Downloader.rb +5 -3
- data/lib/postrunner/FFS_Activity.rb +1 -0
- data/lib/postrunner/FFS_Device.rb +15 -2
- data/lib/postrunner/FitFileStore.rb +17 -11
- data/lib/postrunner/Main.rb +12 -4
- data/lib/postrunner/UserProfileView.rb +29 -16
- data/lib/postrunner/version.rb +1 -1
- data/postrunner.gemspec +2 -2
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d8de6ac38768c84e90eb47ddb49429d7a74160b1
|
4
|
+
data.tar.gz: 7816eba71ed5b6f90eef7d7ae15080c19d548624
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3302269a229d363cfc75bbea304879f06c37e8c2e6c1706ebece3ce6b662b02a026315df59e7494de230c5c3cab3fb81b925bd0ef89f4b81b1280805b422b138
|
7
|
+
data.tar.gz: e8179a0afb54a171ba200480cf498523688acffa9f3568b8a2ae1e272a5bdc4c863449c6c43b201dafe6223afeb64f64ca3520799e93317f4aec8114cdcb64f2
|
data/lib/postrunner/Activity.rb
CHANGED
@@ -95,6 +95,7 @@ module PostRunner
|
|
95
95
|
'challenge' => 'Challenge',
|
96
96
|
'indoor_skiing' => 'Indoor Skiing',
|
97
97
|
'cardio_training' => 'Cardio Training',
|
98
|
+
'virtual_activity' => 'Virtual Activity',
|
98
99
|
'all' => 'All'
|
99
100
|
}
|
100
101
|
|
@@ -379,7 +380,7 @@ module PostRunner
|
|
379
380
|
end
|
380
381
|
|
381
382
|
def activity_sub_type
|
382
|
-
ActivitySubTypes[@sub_sport] || 'Undefined'
|
383
|
+
ActivitySubTypes[@sub_sport] || 'Undefined "#{@sub_sport}"'
|
383
384
|
end
|
384
385
|
|
385
386
|
def distance(timestamp, unit_system)
|
@@ -87,6 +87,7 @@ module PostRunner
|
|
87
87
|
{ :metric => 'km', :statute => 'mi'}) ])
|
88
88
|
end
|
89
89
|
t.row([ 'Time:', secsToHMS(session.total_timer_time) ])
|
90
|
+
t.row([ 'Elapsed Time:', secsToHMS(session.total_elapsed_time) ])
|
90
91
|
t.row([ 'Avg. Speed:',
|
91
92
|
local_value(session, 'avg_speed', '%.1f %s',
|
92
93
|
{ :metric => 'km/h', :statute => 'mph' }) ])
|
@@ -145,6 +146,14 @@ module PostRunner
|
|
145
146
|
t.row([ 'Aerobic Training Effect:', session.total_training_effect ])
|
146
147
|
end
|
147
148
|
|
149
|
+
if (p_epoc = peak_epoc) > 0.0
|
150
|
+
t.row([ 'Peak EPOC:', "%.0f ml/kg" % p_epoc ])
|
151
|
+
end
|
152
|
+
|
153
|
+
if (trimp = trimp_exp) > 0.0
|
154
|
+
t.row([ 'TRIMP:', trimp.round ])
|
155
|
+
end
|
156
|
+
|
148
157
|
rec_info = @fit_activity.recovery_info
|
149
158
|
t.row([ 'Ignored Recovery Time:',
|
150
159
|
rec_info ? secsToDHMS(rec_info * 60) : '-' ])
|
@@ -361,6 +370,89 @@ module PostRunner
|
|
361
370
|
end
|
362
371
|
end
|
363
372
|
|
373
|
+
def trimp_exp
|
374
|
+
# According to Bannister/Morton
|
375
|
+
# TRIMPexp = sum(D x HRr x 0.64e^y)
|
376
|
+
# Where
|
377
|
+
#
|
378
|
+
# D is the duration in minutes at a particular Heart Rate
|
379
|
+
# HRr is the Heart Rate as a fraction of Heart Rate Reserve
|
380
|
+
# y is the HRr multiplied by 1.92 for men and 1.67 for women.
|
381
|
+
return 0.0 unless (user_data = @fit_activity.user_data.first)
|
382
|
+
|
383
|
+
user_profile = @fit_activity.user_profiles.first
|
384
|
+
hr_zones = @fit_activity.heart_rate_zones.first
|
385
|
+
session = @fit_activity.sessions[0]
|
386
|
+
|
387
|
+
unless (user_profile && (rest_hr = user_profile.resting_heart_rate)) ||
|
388
|
+
(hr_zones && (rest_hr = hr_zones.resting_heart_rate))
|
389
|
+
# We must have a valid resting heart rate to compute TRIMP.
|
390
|
+
return 0.0
|
391
|
+
end
|
392
|
+
unless (user_data && (max_hr = user_data.max_hr)) ||
|
393
|
+
(hr_zones && (max_hr = hr_zones.max_heart_rate))
|
394
|
+
# We must have a valid maximum heart rate to compute TRIMP.
|
395
|
+
return 0.0
|
396
|
+
end
|
397
|
+
unless (session && session.avg_heart_rate &&
|
398
|
+
avg_hr = session.avg_heart_rate)
|
399
|
+
return 0.0
|
400
|
+
end
|
401
|
+
|
402
|
+
sex_factor = user_data.gender == 'male' ? 1.92 : 1.67
|
403
|
+
|
404
|
+
# Instead of using the average heart rate for the whole activity we
|
405
|
+
# apply the equation for each heart rate sample and accumulate them.
|
406
|
+
sum = 0.0
|
407
|
+
prev_timestamp = nil
|
408
|
+
@activity.fit_activity.records.each do |r|
|
409
|
+
# We need a valid timestmap and a valid previous timestamp. If they
|
410
|
+
# are more than 10 seconds appart we discard the values as there was
|
411
|
+
# likely a pause in the activity.
|
412
|
+
if prev_timestamp && r.timestamp && r.heart_rate &&
|
413
|
+
r.timestamp - prev_timestamp <= 10
|
414
|
+
# Compute the heart rate as fraction of the heart rate reserve
|
415
|
+
hr_r = (r.heart_rate - rest_hr).to_f / (max_hr - rest_hr)
|
416
|
+
|
417
|
+
duration_min = (r.timestamp - prev_timestamp) / 60.0
|
418
|
+
#sum += duration_min * hr_r * 0.64 * Math.exp(sex_factor * hr_r)
|
419
|
+
sum += duration_min * hr_r * 0.64 * Math.exp(sex_factor * hr_r)
|
420
|
+
end
|
421
|
+
|
422
|
+
prev_timestamp = r.timestamp
|
423
|
+
end
|
424
|
+
|
425
|
+
sum
|
426
|
+
|
427
|
+
# Alternatively here is an avarage HR based implementation
|
428
|
+
# hr_r = (session.avg_heart_rate - rest_hr).to_f / (max_hr - rest_hr)
|
429
|
+
# duration_min = session.total_elapsed_time / 60.0
|
430
|
+
# duration_min * hr_r * 0.64 * Math.exp(sex_factor * hr_r)
|
431
|
+
end
|
432
|
+
|
433
|
+
def peak_epoc
|
434
|
+
# Peak EPOC value according to figure 2 in the following white paper by
|
435
|
+
# FristBeat:
|
436
|
+
# https://www.firstbeat.com/wp-content/uploads/2015/10/white_paper_training_effect.pdf
|
437
|
+
unless @fit_activity.physiological_metrics &&
|
438
|
+
(pm = @fit_activity.physiological_metrics.last) &&
|
439
|
+
(te = pm.aerobic_training_effect)
|
440
|
+
return 0.0
|
441
|
+
end
|
442
|
+
unless (user_data = @fit_activity.user_data.first) &&
|
443
|
+
(ac = user_data.activity_class)
|
444
|
+
return 0.0
|
445
|
+
end
|
446
|
+
|
447
|
+
# The following formula was taken from
|
448
|
+
# http://www.movescount.com/apps/app10020404-EPOC_from_TE
|
449
|
+
# It apparently approximates the graph in figure 2 in the FirstBeat
|
450
|
+
# paper.
|
451
|
+
epoc = -11.0 + te * (20.0 + te * (-47.0/4.0 + te * (3.0 - te / 4.0)))
|
452
|
+
(-102.0 + te * (759.0 / 4.0 + te * (-2867.0 / 24.0 +
|
453
|
+
te * (139.0 / 4.0 - 73.0 / 24.0 * te))) - epoc) / 10.0 * ac + epoc
|
454
|
+
end
|
455
|
+
|
364
456
|
end
|
365
457
|
|
366
458
|
end
|
data/lib/postrunner/ChartView.rb
CHANGED
@@ -290,10 +290,10 @@ EOT
|
|
290
290
|
last_value = nil
|
291
291
|
last_timestamp = nil
|
292
292
|
@activity.fit_activity.records.each do |r|
|
293
|
-
if last_timestamp && (r.timestamp - last_timestamp) >
|
293
|
+
if last_timestamp && (r.timestamp - last_timestamp) > 10.0
|
294
294
|
# We have a gap in the values that is longer than 5 seconds. We'll
|
295
295
|
# finish the line and start a new one later.
|
296
|
-
data_set << [ (
|
296
|
+
data_set << [ (last_timestamp - start_time + 1).to_i * 1000, nil ]
|
297
297
|
end
|
298
298
|
if (value = r.get_as(chart[:id], chart[:unit] || ''))
|
299
299
|
if chart[:id] == 'pace'
|
@@ -310,7 +310,7 @@ EOT
|
|
310
310
|
min_value = value if (min_value.nil? || min_value > value)
|
311
311
|
end
|
312
312
|
end
|
313
|
-
|
313
|
+
if value
|
314
314
|
data_set << [ (r.timestamp - start_time).to_i * 1000, value ]
|
315
315
|
end
|
316
316
|
last_value = value
|
@@ -337,7 +337,7 @@ EOT
|
|
337
337
|
#{chart[:colors] ? "color: \"#{chart[:colors]}\"," : ''}
|
338
338
|
lines: { show: true#{chart[:id] == 'pace' ? '' :
|
339
339
|
', fill: true'} } } ],
|
340
|
-
{ xaxis: { mode: "time" },
|
340
|
+
{ xaxis: { mode: "time", min: 0.0 },
|
341
341
|
grid: { markings: lap_marks, hoverable: true }
|
342
342
|
EOT
|
343
343
|
if chart[:id] == 'pace'
|
@@ -417,7 +417,7 @@ EOT
|
|
417
417
|
" fillColor: \"#{chart[:colors][index][0]}\", " +
|
418
418
|
" fill: true, radius: 2 } }"
|
419
419
|
end.join(', ')
|
420
|
-
s << "], { xaxis: { mode: \"time\" }, " +
|
420
|
+
s << "], { xaxis: { mode: \"time\", min: 0.0 }, " +
|
421
421
|
(chart[:id] == 'gct_balance' ? gct_balance_yaxis(data_sets) : '') +
|
422
422
|
" grid: { markings: lap_marks, hoverable: true } });\n"
|
423
423
|
s << lap_mark_labels(chart_id, start_time)
|
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# = DeviceList.rb -- PostRunner - Manage the data from your Garmin sport devices.
|
5
5
|
#
|
6
|
-
# Copyright (c) 2014, 2015 by Chris Schlaeger <cs@taskjuggler.org>
|
6
|
+
# Copyright (c) 2014, 2015, 2018 by Chris Schlaeger <cs@taskjuggler.org>
|
7
7
|
#
|
8
8
|
# This program is free software; you can redistribute it and/or modify
|
9
9
|
# it under the terms of version 2 of the GNU General Public License as
|
@@ -12,6 +12,7 @@
|
|
12
12
|
|
13
13
|
require 'uri'
|
14
14
|
require 'net/http'
|
15
|
+
require 'net/https'
|
15
16
|
|
16
17
|
module PostRunner
|
17
18
|
|
@@ -20,7 +21,7 @@ module PostRunner
|
|
20
21
|
# devices pick up this file under GARMIN/GARMIN/REMOTESW/EPO.BIN.
|
21
22
|
class EPO_Downloader
|
22
23
|
|
23
|
-
@@URI = URI('
|
24
|
+
@@URI = URI('https://omt.garmin.com/Rce/ProtobufApi/EphemerisService/GetEphemerisData')
|
24
25
|
# This is the payload of the POST request. It was taken from
|
25
26
|
# http://www.kluenter.de/garmin-ephemeris-files-and-linux/. It may contain
|
26
27
|
# a product ID or serial number.
|
@@ -33,7 +34,8 @@ module PostRunner
|
|
33
34
|
|
34
35
|
# Create an EPO_Downloader object.
|
35
36
|
def initialize
|
36
|
-
@
|
37
|
+
@https = Net::HTTP.new(@@URI.host, @@URI.port)
|
38
|
+
@https.use_ssl = true
|
37
39
|
@request = Net::HTTP::Post.new(@@URI.path, initheader = @@HEADER)
|
38
40
|
@request.body = @@POST_DATA
|
39
41
|
end
|
@@ -57,7 +59,7 @@ module PostRunner
|
|
57
59
|
|
58
60
|
def get_epo_from_server
|
59
61
|
begin
|
60
|
-
res = @
|
62
|
+
res = @https.request(@request)
|
61
63
|
rescue => e
|
62
64
|
Log.error "Extended Prediction Orbit (EPO) data download error: " +
|
63
65
|
e.message
|
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# = FFS_Device.rb -- PostRunner - Manage the data from your Garmin sport devices.
|
5
5
|
#
|
6
|
-
# Copyright (c) 2015, 2016 by Chris Schlaeger <cs@taskjuggler.org>
|
6
|
+
# Copyright (c) 2015, 2016, 2018 by Chris Schlaeger <cs@taskjuggler.org>
|
7
7
|
#
|
8
8
|
# This program is free software; you can redistribute it and/or modify
|
9
9
|
# it under the terms of version 2 of the GNU General Public License as
|
@@ -23,7 +23,7 @@ module PostRunner
|
|
23
23
|
# dashes. All objects are transparently stored in the PEROBS::Store.
|
24
24
|
class FFS_Device < PEROBS::Object
|
25
25
|
|
26
|
-
attr_persist :activities, :monitorings, :short_uid, :long_uid
|
26
|
+
attr_persist :activities, :monitorings, :metrics, :short_uid, :long_uid
|
27
27
|
|
28
28
|
# Create a new FFS_Device object.
|
29
29
|
# @param p [PEROBS::Handle] p
|
@@ -41,6 +41,7 @@ module PostRunner
|
|
41
41
|
def restore
|
42
42
|
attr_init(:activities) { @store.new(PEROBS::Array) }
|
43
43
|
attr_init(:monitorings) { @store.new(PEROBS::Array) }
|
44
|
+
attr_init(:metrics) { @store.new(PEROBS::Array) }
|
44
45
|
end
|
45
46
|
|
46
47
|
# Add a new FIT file for this device.
|
@@ -61,6 +62,11 @@ module PostRunner
|
|
61
62
|
entities = @monitorings
|
62
63
|
type = 'monitoring'
|
63
64
|
new_entity_class = FFS_Monitoring
|
65
|
+
elsif fit_entity.is_a?(Fit4Ruby::Metrics)
|
66
|
+
entity = metrics_by_file_name(File.basename(fit_file_name))
|
67
|
+
entities = @metrics
|
68
|
+
type = 'metrics'
|
69
|
+
new_entity_class = FFS_Metrics
|
64
70
|
else
|
65
71
|
Log.fatal "Unsupported FIT entity #{fit_entity.class}"
|
66
72
|
end
|
@@ -130,6 +136,13 @@ module PostRunner
|
|
130
136
|
@monitorings.find { |a| a.fit_file_name == file_name }
|
131
137
|
end
|
132
138
|
|
139
|
+
# Return the metrics with the given file name.
|
140
|
+
# @param file_name [String] Base name of the fit file.
|
141
|
+
# @return [FFS_Activity] Corresponding FFS_Metrics or nil.
|
142
|
+
def metrics_by_file_name(file_name)
|
143
|
+
@metrics.find { |a| a.fit_file_name == file_name }
|
144
|
+
end
|
145
|
+
|
133
146
|
# Return all monitorings that overlap with the time interval given by
|
134
147
|
# from_time and to_time.
|
135
148
|
# @param from_time [Time] start time of the interval
|
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# = FitFileStore.rb -- PostRunner - Manage the data from your Garmin sport devices.
|
5
5
|
#
|
6
|
-
# Copyright (c) 2014, 2015, 2016 by Chris Schlaeger <cs@taskjuggler.org>
|
6
|
+
# Copyright (c) 2014, 2015, 2016, 2018 by Chris Schlaeger <cs@taskjuggler.org>
|
7
7
|
#
|
8
8
|
# This program is free software; you can redistribute it and/or modify
|
9
9
|
# it under the terms of version 2 of the GNU General Public License as
|
@@ -80,20 +80,15 @@ module PostRunner
|
|
80
80
|
# @return [FFS_Activity or FFS_Monitoring] Corresponding entry in the
|
81
81
|
# FitFileStore or nil if file could not be added.
|
82
82
|
def add_fit_file(fit_file_name, fit_entity = nil, overwrite = false)
|
83
|
-
|
84
|
-
if @store['fit_file_md5sums'].include?(md5sum)
|
85
|
-
# The FIT file is already stored in the DB.
|
86
|
-
return nil unless overwrite
|
87
|
-
end
|
88
|
-
|
89
|
-
# If we the file hasn't been read yet, read it in as a
|
83
|
+
# If the file hasn't been read yet, read it in as a
|
90
84
|
# Fit4Ruby::Activity or Fit4Ruby::Monitoring entity.
|
91
85
|
unless fit_entity
|
92
86
|
return nil unless (fit_entity = read_fit_file(fit_file_name))
|
93
87
|
end
|
94
88
|
|
95
89
|
unless [ Fit4Ruby::Activity,
|
96
|
-
Fit4Ruby::Monitoring_B
|
90
|
+
Fit4Ruby::Monitoring_B,
|
91
|
+
Fit4Ruby::Metrics ].include?(fit_entity.class)
|
97
92
|
Log.fatal "Unsupported FIT file type #{fit_entity.class}"
|
98
93
|
end
|
99
94
|
|
@@ -451,7 +446,8 @@ module PostRunner
|
|
451
446
|
|
452
447
|
def extract_fit_file_id(fit_entity)
|
453
448
|
unless (fid = fit_entity.file_id)
|
454
|
-
Log.
|
449
|
+
Log.error 'FIT file has no file_id section'
|
450
|
+
return nil
|
455
451
|
end
|
456
452
|
|
457
453
|
if fid.manufacturer == 'garmin' &&
|
@@ -470,10 +466,20 @@ module PostRunner
|
|
470
466
|
}
|
471
467
|
end
|
472
468
|
end
|
473
|
-
Log.
|
469
|
+
Log.error "Fit entity has no device info for 0"
|
470
|
+
return nil
|
474
471
|
else
|
475
472
|
# And for all properly developed devices we can just look at the
|
476
473
|
# file_id section.
|
474
|
+
if fid.manufacturer.nil? ||
|
475
|
+
fid.manufacturer[0..'Undocumented value'.length - 1] ==
|
476
|
+
'Undocumented value'
|
477
|
+
Log.error "Cannot store FIT files for unknown manufacturer " +
|
478
|
+
fid.manufacturer
|
479
|
+
return nil
|
480
|
+
end
|
481
|
+
fid.serial_number ||= 0
|
482
|
+
|
477
483
|
return {
|
478
484
|
:manufacturer => fid.manufacturer,
|
479
485
|
:product => fid.garmin_product || fid.product,
|
data/lib/postrunner/Main.rb
CHANGED
@@ -57,7 +57,9 @@ module PostRunner
|
|
57
57
|
create_directory(@db_dir, 'PostRunner data')
|
58
58
|
ensure_flat_file_db
|
59
59
|
@db = PEROBS::Store.new(File.join(@db_dir, 'database'),
|
60
|
-
{ :engine => PEROBS::FlatFileDB
|
60
|
+
{ :engine => PEROBS::FlatFileDB,
|
61
|
+
:progressmeter =>
|
62
|
+
PEROBS::ConsoleProgressMeter.new })
|
61
63
|
# Create a hash to store configuration data in the store unless it
|
62
64
|
# exists already.
|
63
65
|
cfg = (@db['config'] ||= @db.new(PEROBS::Hash))
|
@@ -252,7 +254,7 @@ weekly [ <YYYY-MM-DD> ]
|
|
252
254
|
week. If no date is given, yesterday's week will be used.
|
253
255
|
|
254
256
|
|
255
|
-
<fit file> An absolute or relative name of a .FIT file.
|
257
|
+
<fit file> An absolute or relative name of a .FIT or .fit file.
|
256
258
|
|
257
259
|
<ref> The index or a range of indexes to activities in the database.
|
258
260
|
:1 is the newest imported activity
|
@@ -437,12 +439,12 @@ EOT
|
|
437
439
|
|
438
440
|
def process_files(files_or_dirs, command)
|
439
441
|
if files_or_dirs.empty?
|
440
|
-
Log.abort("You must provide at least one .FIT file name.")
|
442
|
+
Log.abort("You must provide at least one .FIT or .fit file name.")
|
441
443
|
end
|
442
444
|
|
443
445
|
files_or_dirs.each do |fod|
|
444
446
|
if File.directory?(fod)
|
445
|
-
Dir.glob(File.join(fod, '*.FIT')).each do |file|
|
447
|
+
Dir.glob(File.join(fod, '*.FIT'), File::FNM_CASEFOLD).each do |file|
|
446
448
|
process_file(file, command)
|
447
449
|
end
|
448
450
|
else
|
@@ -472,6 +474,12 @@ EOT
|
|
472
474
|
# @return [TrueClass, FalseClass] true if file was successfully imported,
|
473
475
|
# false otherwise
|
474
476
|
def import_fit_file(fit_file_name)
|
477
|
+
md5sum = FitFileStore.calc_md5_sum(fit_file_name)
|
478
|
+
if @ffs.store['fit_file_md5sums'].include?(md5sum)
|
479
|
+
# The FIT file is already stored in the DB.
|
480
|
+
return nil unless @force
|
481
|
+
end
|
482
|
+
|
475
483
|
begin
|
476
484
|
fit_entity = Fit4Ruby.read(fit_file_name)
|
477
485
|
rescue Fit4Ruby::Error
|
@@ -24,14 +24,14 @@ module PostRunner
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def to_html(doc)
|
27
|
-
return nil if @fit_activity.
|
27
|
+
return nil if @fit_activity.user_data.empty?
|
28
28
|
|
29
29
|
ViewFrame.new('user_profile', 'User Profile', 600, profile,
|
30
30
|
true).to_html(doc)
|
31
31
|
end
|
32
32
|
|
33
33
|
def to_s
|
34
|
-
return '' if @fit_activity.
|
34
|
+
return '' if @fit_activity.user_data.empty?
|
35
35
|
profile.to_s
|
36
36
|
end
|
37
37
|
|
@@ -39,31 +39,44 @@ module PostRunner
|
|
39
39
|
|
40
40
|
def profile
|
41
41
|
t = FlexiTable.new
|
42
|
-
|
43
|
-
|
42
|
+
|
43
|
+
user_data = @fit_activity.user_data.first
|
44
|
+
user_profile = @fit_activity.user_profiles.first
|
45
|
+
hr_zones = @fit_activity.heart_rate_zones.first
|
46
|
+
|
47
|
+
if user_data.height
|
44
48
|
unit = { :metric => 'm', :statute => 'ft' }[@unit_system]
|
45
|
-
height =
|
49
|
+
height = user_data.get_as('height', unit)
|
46
50
|
t.cell('Height:', { :width => '40%' })
|
47
51
|
t.cell("#{'%.2f' % height} #{unit}", { :width => '60%' })
|
48
52
|
t.new_row
|
49
53
|
end
|
50
|
-
if
|
54
|
+
if user_data.weight
|
51
55
|
unit = { :metric => 'kg', :statute => 'lbs' }[@unit_system]
|
52
|
-
weight =
|
56
|
+
weight = user_data.get_as('weight', unit)
|
53
57
|
t.row([ 'Weight:', "#{'%.1f' % weight} #{unit}" ])
|
54
58
|
end
|
55
|
-
t.row([ 'Gender:',
|
56
|
-
t.row([ 'Age:', "#{
|
57
|
-
|
58
|
-
|
59
|
+
t.row([ 'Gender:', user_data.gender ]) if user_data.gender
|
60
|
+
t.row([ 'Age:', "#{user_data.age} years" ]) if user_data.age
|
61
|
+
if (user_profile && (rest_hr = user_profile.resting_heart_rate)) ||
|
62
|
+
(hr_zones && (rest_hr = hr_zones.resting_heart_rate))
|
63
|
+
t.row([ 'Resting Heart Rate:', "#{rest_hr} bpm" ])
|
64
|
+
end
|
65
|
+
if (max_hr = user_data.max_hr) ||
|
66
|
+
(max_hr = hr_zones.max_heart_rate)
|
67
|
+
t.row([ 'Max. Heart Rate:', "#{max_hr} bpm" ])
|
68
|
+
end
|
69
|
+
if (lthr = user_data.running_lactate_threshold_heart_rate)
|
59
70
|
t.row([ 'Running LTHR:', "#{lthr} bpm" ])
|
60
71
|
end
|
61
|
-
if
|
62
|
-
t.row([ 'Activity Class:',
|
72
|
+
if (activity_class = user_data.activity_class)
|
73
|
+
t.row([ 'Activity Class:', activity_class ])
|
74
|
+
end
|
75
|
+
if (metmax = user_data.metmax)
|
76
|
+
t.row([ 'METmax:', "#{metmax} MET" ])
|
63
77
|
end
|
64
|
-
if
|
65
|
-
t.row([ '
|
66
|
-
t.row([ 'VO2max:', "#{'%.1f' % (profile.metmax * 3.5)} ml/kg/min" ])
|
78
|
+
if (vo2max = @fit_activity.vo2max)
|
79
|
+
t.row([ 'VO2max:', "#{'%.1f' % vo2max} ml/kg/min" ])
|
67
80
|
end
|
68
81
|
t
|
69
82
|
end
|
data/lib/postrunner/version.rb
CHANGED
data/postrunner.gemspec
CHANGED
@@ -28,8 +28,8 @@ operating systems as well.}
|
|
28
28
|
spec.require_paths = ["lib"]
|
29
29
|
spec.required_ruby_version = '>=2.0'
|
30
30
|
|
31
|
-
spec.add_dependency 'fit4ruby', '~> 2.0
|
32
|
-
spec.add_dependency 'perobs', '~> 4.
|
31
|
+
spec.add_dependency 'fit4ruby', '~> 3.2.0'
|
32
|
+
spec.add_dependency 'perobs', '~> 4.1.0'
|
33
33
|
spec.add_dependency 'nokogiri', '~> 1.6'
|
34
34
|
|
35
35
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: postrunner
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Schlaeger
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-06-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fit4ruby
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 2.0
|
19
|
+
version: 3.2.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 2.0
|
26
|
+
version: 3.2.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: perobs
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 4.
|
33
|
+
version: 4.1.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 4.
|
40
|
+
version: 4.1.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: nokogiri
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|