postrunner 1.0.2 → 1.1.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/.github/workflows/ruby.yml +33 -0
- data/README.md +30 -6
- data/Rakefile +7 -22
- data/lib/postrunner/Activity.rb +67 -0
- data/lib/postrunner/ActivitySummary.rb +18 -16
- data/lib/postrunner/ActivityView.rb +2 -1
- data/lib/postrunner/ChartView.rb +35 -3
- data/lib/postrunner/DailyMonitoringView.rb +1 -1
- data/lib/postrunner/DeviceList.rb +21 -0
- data/lib/postrunner/EventList.rb +12 -2
- data/lib/postrunner/FFS_Activity.rb +1 -0
- data/lib/postrunner/FitFileStore.rb +1 -3
- data/lib/postrunner/Main.rb +41 -16
- data/lib/postrunner/MonitoringStatistics.rb +8 -4
- data/lib/postrunner/PersonalRecords.rb +6 -2
- data/lib/postrunner/version.rb +1 -1
- data/misc/jquery/jquery-3.5.1.min.js +2 -0
- data/misc/openlayers/ol.css +2 -1
- data/misc/openlayers/ol.js +9 -948
- data/postrunner.gemspec +7 -7
- metadata +32 -34
- data/misc/flot/jquery.js +0 -9472
- data/misc/flot/jquery.min.js +0 -5
- data/misc/jquery/jquery-2.1.1.min.js +0 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b6df1b75dc7669dd99287084a3bc96c4528b2273b37d8203a75b12d9e26b0a2b
|
|
4
|
+
data.tar.gz: c2cf4a588aebb579e208f62b2c2c9d1ce76798ac482b4f1b0838bb92554691d1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d02b2921d8f7aedf63dc07ebf8d21765e3ab6e4e36872586eb5fffbca8d265e65a64c6564ba8a88406de39e4f9d534d93cd903563a9f28f3e44d5ddeaf9ce8d7
|
|
7
|
+
data.tar.gz: 8c6d305975888ef68fda07e2793d78da8d79c2d5aa114efa5b5cc86b4e97ff1d2882765772db3e26a99f33343278e29aa8bb4f208f4caf5601989ba4ec37f758
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# This workflow uses actions that are not certified by GitHub.
|
|
2
|
+
# They are provided by a third-party and are governed by
|
|
3
|
+
# separate terms of service, privacy policy, and support
|
|
4
|
+
# documentation.
|
|
5
|
+
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
|
|
6
|
+
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
|
|
7
|
+
|
|
8
|
+
name: Ruby
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
push:
|
|
12
|
+
branches: [ master ]
|
|
13
|
+
pull_request:
|
|
14
|
+
branches: [ master ]
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
test:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
strategy:
|
|
20
|
+
fail-fast: false
|
|
21
|
+
matrix:
|
|
22
|
+
os: [ ubuntu]
|
|
23
|
+
ruby: [2.4, 2.5, 2.6, 2.7, 3.0, head]
|
|
24
|
+
continue-on-error: ${{ endsWith(matrix.ruby, 'head') }}
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v2
|
|
27
|
+
- uses: ruby/setup-ruby@v1
|
|
28
|
+
with:
|
|
29
|
+
ruby-version: ${{ matrix.ruby }}
|
|
30
|
+
bundler-cache: true
|
|
31
|
+
|
|
32
|
+
- name: Test with RSpec
|
|
33
|
+
run: bundle exec rspec
|
data/README.md
CHANGED
|
@@ -48,23 +48,47 @@ typing.
|
|
|
48
48
|
|
|
49
49
|
## Usage
|
|
50
50
|
|
|
51
|
+
### Mounting the watch
|
|
52
|
+
|
|
53
|
+
Watches that expose their data as FAT file system. Typically after connecting
|
|
54
|
+
your garmin watch, they are mount automatically if you have installed the
|
|
55
|
+
udev package.
|
|
56
|
+
|
|
57
|
+
For watches that expose their data via MTP (Media Transfer Procotol). For
|
|
58
|
+
Debian buster running `sudo apt install jmtpfs mtp-tools` will install the
|
|
59
|
+
needed packages.
|
|
60
|
+
|
|
61
|
+
`mkdir /tmp/forerunner; jmtpfs /tmp/forerunner`
|
|
62
|
+
|
|
63
|
+
This has been tested with a Garmin Forerunner 945.
|
|
64
|
+
|
|
65
|
+
For more information about MTP under Windows have a look at the
|
|
66
|
+
[Garmin FAQ](https://support.garmin.com/?faq=Itl8M6ARrh4gBQMHFqdqK8)
|
|
67
|
+
|
|
51
68
|
### Importing FIT files
|
|
52
69
|
|
|
53
70
|
To get started you need to connect your device to your computer and
|
|
54
|
-
mount it as a disk drive. Only devices that expose their data as FAT file
|
|
71
|
+
mount it as a disk drive. Only devices that expose their data as FAT or MTP file
|
|
55
72
|
system are supported. Older devices use proprietary drivers and are
|
|
56
73
|
not supported by PostRunner. Once the device is mounted find out the
|
|
57
74
|
full path to the directory that contains your FIT files. You can then
|
|
58
75
|
import all files on the device.
|
|
59
76
|
|
|
77
|
+
* for USB-FAT
|
|
60
78
|
```
|
|
61
79
|
$ postrunner import /run/media/$USER/GARMIN/GARMIN/ACTIVITY/
|
|
62
80
|
```
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
81
|
+
The above command assumes that your device is mounted as /run/media/$USER.
|
|
82
|
+
Please replace $USER with your login name and the path with the path to
|
|
83
|
+
your device.
|
|
84
|
+
|
|
85
|
+
* for MTP (assuming you mounted it as described above)
|
|
86
|
+
```
|
|
87
|
+
$ postrunner import /tmp/forerunner/Primary/GARMIN/Activity
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
* Note
|
|
91
|
+
Files that have been imported previously will not be imported again.
|
|
68
92
|
|
|
69
93
|
### Viewing FIT file data on the console
|
|
70
94
|
|
data/Rakefile
CHANGED
|
@@ -1,24 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
$:.unshift lib unless $:.include?(lib)
|
|
6
|
-
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
require_relative 'lib/postrunner/version'
|
|
3
|
+
require 'rake'
|
|
4
|
+
require 'rspec'
|
|
7
5
|
require "bundler/gem_tasks"
|
|
8
|
-
require
|
|
9
|
-
require 'rake/clean'
|
|
10
|
-
require 'yard'
|
|
11
|
-
YARD::Rake::YardocTask.new
|
|
12
|
-
|
|
13
|
-
Dir.glob( 'tasks/*.rake').each do |fn|
|
|
14
|
-
begin
|
|
15
|
-
load fn;
|
|
16
|
-
rescue LoadError
|
|
17
|
-
puts "#{fn.split('/')[1]} tasks unavailable: #{$!}"
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
task :default => :spec
|
|
22
|
-
task :test => :spec
|
|
6
|
+
require 'rspec/core/rake_task'
|
|
23
7
|
|
|
24
|
-
|
|
8
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
9
|
+
|
data/lib/postrunner/Activity.rb
CHANGED
|
@@ -65,6 +65,37 @@ module PostRunner
|
|
|
65
65
|
'hiking' => 'Hiking',
|
|
66
66
|
'multisport' => 'Multisport',
|
|
67
67
|
'paddling' => 'Paddling',
|
|
68
|
+
'training' => 'Training',
|
|
69
|
+
'flying' => 'Flying',
|
|
70
|
+
'e_biking' => 'E-Biking',
|
|
71
|
+
'motorcycling' => 'Motor Cycling',
|
|
72
|
+
'boating' => 'Boating',
|
|
73
|
+
'driving' => 'Driving',
|
|
74
|
+
'golf' => 'Golf',
|
|
75
|
+
'hang_gliding' => 'Hang Gliding',
|
|
76
|
+
'horseback_riding' => 'Horseback Riding',
|
|
77
|
+
'hunting' => 'Hunting',
|
|
78
|
+
'fishing' => 'Fishing',
|
|
79
|
+
'inline_skating' => 'Inline Skating',
|
|
80
|
+
'rock_climbing' => 'Rock Climbing',
|
|
81
|
+
'sailing' => 'Sailing',
|
|
82
|
+
'ice_skating' => 'Ice Skating',
|
|
83
|
+
'sky_diving' => 'Sky Diving',
|
|
84
|
+
'snowshoeing' => 'Snowshoeing',
|
|
85
|
+
'snowmobiling' => 'Snowmobiling',
|
|
86
|
+
'stand_up_paddleboarding' => 'Stand Up Paddleboarding',
|
|
87
|
+
'surfing' => 'Surfing',
|
|
88
|
+
'wakeboarding' => 'Wakeboarding',
|
|
89
|
+
'water_skiing' => 'Water Skiing',
|
|
90
|
+
'kayaking' => 'Kayaking',
|
|
91
|
+
'rafting' => 'Rafting',
|
|
92
|
+
'windsurfing' => 'Windsurfing',
|
|
93
|
+
'kitesurfing' => 'Kitesurfing',
|
|
94
|
+
'tactical' => 'Tactical',
|
|
95
|
+
'jumpmaster' => 'Jumpmaster',
|
|
96
|
+
'boxing' => 'Boxing',
|
|
97
|
+
'floor_climbing' => 'Floor Climbing',
|
|
98
|
+
'diving' => 'Diving',
|
|
68
99
|
'all' => 'All'
|
|
69
100
|
}
|
|
70
101
|
ActivitySubTypes = {
|
|
@@ -95,7 +126,43 @@ module PostRunner
|
|
|
95
126
|
'challenge' => 'Challenge',
|
|
96
127
|
'indoor_skiing' => 'Indoor Skiing',
|
|
97
128
|
'cardio_training' => 'Cardio Training',
|
|
129
|
+
'e_bike_fitness' => 'E Bike Fittness',
|
|
130
|
+
'bmx' => 'BMX',
|
|
131
|
+
'casual_walking' => 'Casual Walking',
|
|
132
|
+
'speed_walking' => 'Speed Walking',
|
|
133
|
+
'bike_to_run_transition' => 'Bike to Run Transition',
|
|
134
|
+
'run_to_bike_transition' => 'Run to Bike Transition',
|
|
135
|
+
'swim_to_bike_transition' => 'Swim to Bike Transition',
|
|
136
|
+
'atv' => 'ATV',
|
|
137
|
+
'motocross' => 'Motocross',
|
|
138
|
+
'backcountry' => 'Backcountry',
|
|
139
|
+
'resort' => 'Resort',
|
|
140
|
+
'rc_drone' => 'RC Drone',
|
|
141
|
+
'wingsuit' => 'Wingsuite',
|
|
142
|
+
'whitewater' => 'Whitemaster',
|
|
143
|
+
'skate_skiing' => 'Skate Skiing',
|
|
144
|
+
'yoga' => 'Yoga',
|
|
145
|
+
'pilates' => 'Pilates',
|
|
146
|
+
'indoor_running' => 'Indoor Running',
|
|
147
|
+
'gravel_cycling' => 'Gravel Cycling',
|
|
148
|
+
'e_bike_mountain' => 'E-Bike Mountain',
|
|
149
|
+
'commuting' => 'Commuting',
|
|
150
|
+
'mixed_surface' => 'Mixed Surface',
|
|
151
|
+
'navigate' => 'Navigate',
|
|
152
|
+
'track_me' => 'Track Me',
|
|
153
|
+
'map' => 'Map',
|
|
154
|
+
'single_gas_diving' => 'Single Gas Diving',
|
|
155
|
+
'multi_gas_diving' => 'Multi Gas Diving',
|
|
156
|
+
'gauge_diving' => 'Gauge Diving',
|
|
157
|
+
'apnea_diving' => 'Apnea Diving',
|
|
158
|
+
'apnea_hunting' => 'Apnea Hunting',
|
|
98
159
|
'virtual_activity' => 'Virtual Activity',
|
|
160
|
+
'obstacle' => 'Obstacle',
|
|
161
|
+
'breathing' => 'Breating',
|
|
162
|
+
'sail_race' => 'Sail Race',
|
|
163
|
+
'ultra' => 'Ultra',
|
|
164
|
+
'indoor_climbing' => 'Indoor Climbing',
|
|
165
|
+
'bouldering' => 'Bouldering',
|
|
99
166
|
'all' => 'All'
|
|
100
167
|
}
|
|
101
168
|
|
|
@@ -77,7 +77,9 @@ module PostRunner
|
|
|
77
77
|
t.body
|
|
78
78
|
t.row([ 'Type:', @type ])
|
|
79
79
|
t.row([ 'Sub Type:', @sub_type ])
|
|
80
|
-
t.row([ 'Start Time:', session.start_time])
|
|
80
|
+
t.row([ 'Start Time:', session.start_time.localtime])
|
|
81
|
+
t.row([ 'Elapsed Time:', secsToHMS(session.total_elapsed_time) ])
|
|
82
|
+
t.row([ 'Moving Time:', secsToHMS(session.total_timer_time) ])
|
|
81
83
|
t.row([ 'Distance:',
|
|
82
84
|
local_value(session, 'total_distance', '%.2f %s',
|
|
83
85
|
{ :metric => 'km', :statute => 'mi'}) ])
|
|
@@ -86,26 +88,11 @@ module PostRunner
|
|
|
86
88
|
local_value(@fit_activity, 'total_gps_distance', '%.2f %s',
|
|
87
89
|
{ :metric => 'km', :statute => 'mi'}) ])
|
|
88
90
|
end
|
|
89
|
-
t.row([ 'Time:', secsToHMS(session.total_timer_time) ])
|
|
90
|
-
t.row([ 'Elapsed Time:', secsToHMS(session.total_elapsed_time) ])
|
|
91
91
|
t.row([ 'Avg. Speed:',
|
|
92
92
|
local_value(session, 'avg_speed', '%.1f %s',
|
|
93
93
|
{ :metric => 'km/h', :statute => 'mph' }) ])
|
|
94
94
|
if @activity.sport == 'running' || @activity.sport == 'multisport'
|
|
95
95
|
t.row([ 'Avg. Pace:', pace(session, 'avg_speed') ])
|
|
96
|
-
end
|
|
97
|
-
t.row([ 'Total Ascent:',
|
|
98
|
-
local_value(session, 'total_ascent', '%.0f %s',
|
|
99
|
-
{ :metric => 'm', :statute => 'ft' }) ])
|
|
100
|
-
t.row([ 'Total Descent:',
|
|
101
|
-
local_value(session, 'total_descent', '%.0f %s',
|
|
102
|
-
{ :metric => 'm', :statute => 'ft' }) ])
|
|
103
|
-
t.row([ 'Calories:', "#{session.total_calories} kCal" ])
|
|
104
|
-
t.row([ 'Avg. HR:', session.avg_heart_rate ?
|
|
105
|
-
"#{session.avg_heart_rate} bpm" : '-' ])
|
|
106
|
-
t.row([ 'Max. HR:', session.max_heart_rate ?
|
|
107
|
-
"#{session.max_heart_rate} bpm" : '-' ])
|
|
108
|
-
if @activity.sport == 'running' || @activity.sport == 'multisport'
|
|
109
96
|
t.row([ 'Avg. Run Cadence:',
|
|
110
97
|
session.avg_running_cadence ?
|
|
111
98
|
"#{(2 * session.avg_running_cadence).round} spm" : '-' ])
|
|
@@ -131,6 +118,21 @@ module PostRunner
|
|
|
131
118
|
session.avg_cadence ?
|
|
132
119
|
"#{(2 * session.avg_cadence).round} rpm" : '-' ])
|
|
133
120
|
end
|
|
121
|
+
t.row([ 'Total Ascent:',
|
|
122
|
+
local_value(session, 'total_ascent', '%.0f %s',
|
|
123
|
+
{ :metric => 'm', :statute => 'ft' }) ])
|
|
124
|
+
t.row([ 'Total Descent:',
|
|
125
|
+
local_value(session, 'total_descent', '%.0f %s',
|
|
126
|
+
{ :metric => 'm', :statute => 'ft' }) ])
|
|
127
|
+
t.row([ 'Calories:', "#{session.total_calories} kCal" ])
|
|
128
|
+
|
|
129
|
+
if (est_sweat_loss = session.est_sweat_loss)
|
|
130
|
+
t.row([ 'Est. Sweat Loss:', "#{est_sweat_loss} ml" ])
|
|
131
|
+
end
|
|
132
|
+
t.row([ 'Avg. HR:', session.avg_heart_rate ?
|
|
133
|
+
"#{session.avg_heart_rate} bpm" : '-' ])
|
|
134
|
+
t.row([ 'Max. HR:', session.max_heart_rate ?
|
|
135
|
+
"#{session.max_heart_rate} bpm" : '-' ])
|
|
134
136
|
|
|
135
137
|
if @fit_activity.physiological_metrics &&
|
|
136
138
|
(physiological_metrics = @fit_activity.physiological_metrics.last)
|
|
@@ -53,7 +53,8 @@ module PostRunner
|
|
|
53
53
|
def generate_html(doc)
|
|
54
54
|
doc.unique(:activityview_style) {
|
|
55
55
|
doc.head {
|
|
56
|
-
[ 'jquery/jquery-
|
|
56
|
+
[ 'jquery/jquery-3.5.1.min.js', 'flot/jquery.flot.js',
|
|
57
|
+
#'flot/jquery.flot.time.js' ].each do |js|
|
|
57
58
|
'flot/jquery.flot.time.js' ].each do |js|
|
|
58
59
|
doc.script({ 'language' => 'javascript',
|
|
59
60
|
'type' => 'text/javascript', 'src' => js })
|
data/lib/postrunner/ChartView.rb
CHANGED
|
@@ -30,7 +30,7 @@ module PostRunner
|
|
|
30
30
|
:unit => select_unit('min/km'),
|
|
31
31
|
:graph => :line_graph,
|
|
32
32
|
:colors => '#0A7BEE',
|
|
33
|
-
:show => @sport == 'running' || @sport == 'multisport'
|
|
33
|
+
:show => @sport == 'running' || @sport == 'multisport'
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
:id => 'speed',
|
|
@@ -40,9 +40,17 @@ module PostRunner
|
|
|
40
40
|
:colors => '#0A7BEE',
|
|
41
41
|
:show => @sport != 'running'
|
|
42
42
|
},
|
|
43
|
+
{
|
|
44
|
+
:id => 'Power_18FB2CF01A4B430DAD66988C847421F4',
|
|
45
|
+
:label => 'Power',
|
|
46
|
+
:unit => select_unit('Watts'),
|
|
47
|
+
:graph => :line_graph,
|
|
48
|
+
:colors => '#FFAC2E',
|
|
49
|
+
:show => @sport == 'running' || @sport == 'multisport'
|
|
50
|
+
},
|
|
43
51
|
{
|
|
44
52
|
:id => 'altitude',
|
|
45
|
-
:label => '
|
|
53
|
+
:label => 'Altitude',
|
|
46
54
|
:unit => select_unit('m'),
|
|
47
55
|
:graph => :line_graph,
|
|
48
56
|
:colors => '#5AAA44',
|
|
@@ -127,6 +135,22 @@ module PostRunner
|
|
|
127
135
|
[ '#FF5558', nil ] ],
|
|
128
136
|
:show => @sport == 'running' || @sport == 'multisport'
|
|
129
137
|
},
|
|
138
|
+
{
|
|
139
|
+
:id => 'Form_Power_18FB2CF01A4B430DAD66988C847421F4',
|
|
140
|
+
:label => 'Form Power',
|
|
141
|
+
:unit => select_unit('Watts'),
|
|
142
|
+
:graph => :line_graph,
|
|
143
|
+
:colors => '#CBBB58',
|
|
144
|
+
:show => @sport == 'running' || @sport == 'multisport'
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
:id => 'Leg_Spring_Stiffness_18FB2CF01A4B430DAD66988C847421F4',
|
|
148
|
+
:label => 'Leg Spring Stiffness',
|
|
149
|
+
:unit => select_unit('kN/m'),
|
|
150
|
+
:graph => :line_graph,
|
|
151
|
+
:colors => '#358C88',
|
|
152
|
+
:show => @sport == 'running' || @sport == 'multisport'
|
|
153
|
+
},
|
|
130
154
|
{
|
|
131
155
|
:id => 'stance_time',
|
|
132
156
|
:label => 'Ground Contact Time',
|
|
@@ -157,6 +181,14 @@ module PostRunner
|
|
|
157
181
|
:colors => '#A88BBB',
|
|
158
182
|
:show => @sport == 'cycling'
|
|
159
183
|
},
|
|
184
|
+
{
|
|
185
|
+
:id => "Air_Power_18FB2CF01A4B430DAD66988C847421F4",
|
|
186
|
+
:label => 'Air Power',
|
|
187
|
+
:unit => select_unit('Watts'),
|
|
188
|
+
:graph => :line_graph,
|
|
189
|
+
:colors => '#919498',
|
|
190
|
+
:show => @sport == 'running' || @sport == 'multisport'
|
|
191
|
+
},
|
|
160
192
|
{
|
|
161
193
|
:id => 'temperature',
|
|
162
194
|
:label => 'Temperature',
|
|
@@ -229,7 +261,7 @@ EOT
|
|
|
229
261
|
end
|
|
230
262
|
|
|
231
263
|
def java_script
|
|
232
|
-
s = "$(function() {\n"
|
|
264
|
+
s = "$(document).ready(function() {\n"
|
|
233
265
|
|
|
234
266
|
s << tooltip_div
|
|
235
267
|
@charts.each do |chart|
|
|
@@ -44,7 +44,7 @@ module PostRunner
|
|
|
44
44
|
def generate_html(doc)
|
|
45
45
|
doc.unique(:dailymonitoringview_style) {
|
|
46
46
|
doc.head {
|
|
47
|
-
[ 'jquery/jquery-
|
|
47
|
+
[ 'jquery/jquery-3.5.1.min.js', 'flot/jquery.flot.js',
|
|
48
48
|
'flot/jquery.flot.time.js' ].each do |js|
|
|
49
49
|
doc.script({ 'language' => 'javascript',
|
|
50
50
|
'type' => 'text/javascript', 'src' => js })
|
|
@@ -126,6 +126,19 @@ module PostRunner
|
|
|
126
126
|
t.new_row
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
+
if (ant_id = device.ant_id)
|
|
130
|
+
t.cell('ANT ID:')
|
|
131
|
+
t.cell(ant_id)
|
|
132
|
+
t.new_row
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
if ant_id && (sensor_settings = find_settings_by_ant_id(ant_id)) &&
|
|
136
|
+
(calibration_factor = sensor_settings.calibration_factor)
|
|
137
|
+
t.cell('Calibration Factor')
|
|
138
|
+
t.cell('%.1f' % calibration_factor)
|
|
139
|
+
t.new_row
|
|
140
|
+
end
|
|
141
|
+
|
|
129
142
|
if (rx_ok = device.rx_packets_ok) && (rx_err = device.rx_packets_err)
|
|
130
143
|
t.cell('Packet Errors:')
|
|
131
144
|
t.cell('%d%%' % ((rx_err.to_f / (rx_ok + rx_err)) * 100).to_i)
|
|
@@ -148,6 +161,14 @@ module PostRunner
|
|
|
148
161
|
tables
|
|
149
162
|
end
|
|
150
163
|
|
|
164
|
+
private
|
|
165
|
+
|
|
166
|
+
def find_settings_by_ant_id(ant_id)
|
|
167
|
+
@fit_activity.sensor_settings.find do |sensor|
|
|
168
|
+
sensor.ant_id == ant_id
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
151
172
|
end
|
|
152
173
|
|
|
153
174
|
end
|
data/lib/postrunner/EventList.rb
CHANGED
|
@@ -75,7 +75,7 @@ module PostRunner
|
|
|
75
75
|
case event.event
|
|
76
76
|
when 'timer'
|
|
77
77
|
name = "Timer (#{event.event_type.gsub(/_/, ' ')})"
|
|
78
|
-
value =
|
|
78
|
+
value = '-'
|
|
79
79
|
when 'course_point'
|
|
80
80
|
name = 'Course Point'
|
|
81
81
|
value = event.message_index
|
|
@@ -121,12 +121,19 @@ module PostRunner
|
|
|
121
121
|
when 'comm_timeout'
|
|
122
122
|
name 'Communication timeout'
|
|
123
123
|
value = event.comm_timeout
|
|
124
|
+
when 'off_course'
|
|
125
|
+
name = 'Off Course'
|
|
126
|
+
value = '-'
|
|
124
127
|
when 'recovery_hr'
|
|
125
128
|
name = 'Recovery heart rate'
|
|
126
129
|
value = "#{event.recovery_hr} bpm"
|
|
127
130
|
when 'recovery_time'
|
|
128
131
|
name = 'Recovery time'
|
|
129
|
-
|
|
132
|
+
if event.recovery_time
|
|
133
|
+
value = "#{secsToDHMS(event.recovery_time * 60)}"
|
|
134
|
+
else
|
|
135
|
+
value = '-'
|
|
136
|
+
end
|
|
130
137
|
when 'recovery_info'
|
|
131
138
|
name = 'Recovery info'
|
|
132
139
|
mins = event.recovery_info
|
|
@@ -140,6 +147,9 @@ module PostRunner
|
|
|
140
147
|
when 'lactate_threshold_speed'
|
|
141
148
|
name = 'Lactate Threshold Pace'
|
|
142
149
|
value = pace(event, 'lactate_threshold_speed')
|
|
150
|
+
when 'functional_threshold_power'
|
|
151
|
+
name = 'Functional Threshold Power'
|
|
152
|
+
value = "#{event.functional_threshold_power} W"
|
|
143
153
|
else
|
|
144
154
|
name = event.event
|
|
145
155
|
value = event.data
|
|
@@ -508,9 +508,7 @@ module PostRunner
|
|
|
508
508
|
# Not all FIT file have indexed device sections. In case the device
|
|
509
509
|
# index is nil we'll take the first entry.
|
|
510
510
|
if (di.device_index.nil? || di.device_index == 0) &&
|
|
511
|
-
|
|
512
|
-
(di.garmin_product || di.product) &&
|
|
513
|
-
di.numeric_product && di.serial_number)
|
|
511
|
+
di.numeric_manufacturer && di.numeric_product
|
|
514
512
|
return {
|
|
515
513
|
:manufacturer => di.manufacturer,
|
|
516
514
|
:product => di.garmin_product || di.product,
|
data/lib/postrunner/Main.rb
CHANGED
|
@@ -310,6 +310,7 @@ EOT
|
|
|
310
310
|
handle_version_update
|
|
311
311
|
import_legacy_archive
|
|
312
312
|
|
|
313
|
+
retval = 0
|
|
313
314
|
case (cmd = args.shift)
|
|
314
315
|
when 'check'
|
|
315
316
|
if args.empty?
|
|
@@ -334,7 +335,9 @@ EOT
|
|
|
334
335
|
# is given, use the current date.
|
|
335
336
|
@ffs.monthly_report(day_in_localtime(args, '%Y-%m-01'))
|
|
336
337
|
when 'delete'
|
|
337
|
-
process_activities(args, :delete)
|
|
338
|
+
unless process_activities(args, :delete)
|
|
339
|
+
retval = 1
|
|
340
|
+
end
|
|
338
341
|
when 'dump'
|
|
339
342
|
@filter = Fit4Ruby::FitFilter.new unless @filter
|
|
340
343
|
process_files_or_activities(args, :dump)
|
|
@@ -344,9 +347,13 @@ EOT
|
|
|
344
347
|
if args.empty?
|
|
345
348
|
# If we have no file or directory for the import command, we get the
|
|
346
349
|
# most recently used directory from the runtime config.
|
|
347
|
-
process_files([ @db['config']['import_dir'] ], :import)
|
|
350
|
+
unless process_files([ @db['config']['import_dir'] ], :import)
|
|
351
|
+
retval = 1
|
|
352
|
+
end
|
|
348
353
|
else
|
|
349
|
-
process_files(args, :import)
|
|
354
|
+
unless process_files(args, :import)
|
|
355
|
+
retval = 1
|
|
356
|
+
end
|
|
350
357
|
if args.length == 1 && Dir.exists?(args[0])
|
|
351
358
|
# If only one directory was specified as argument we store the
|
|
352
359
|
# directory for future use.
|
|
@@ -361,7 +368,9 @@ EOT
|
|
|
361
368
|
unless (@name = args.shift)
|
|
362
369
|
Log.abort 'You must provide a new name for the activity'
|
|
363
370
|
end
|
|
364
|
-
process_activities(args, :rename)
|
|
371
|
+
unless process_activities(args, :rename)
|
|
372
|
+
retval = 1
|
|
373
|
+
end
|
|
365
374
|
when 'set'
|
|
366
375
|
unless (@attribute = args.shift)
|
|
367
376
|
Log.abort 'You must specify the attribute you want to change'
|
|
@@ -369,7 +378,9 @@ EOT
|
|
|
369
378
|
unless (@value = args.shift)
|
|
370
379
|
Log.abort 'You must specify the new value for the attribute'
|
|
371
380
|
end
|
|
372
|
-
process_activities(args, :set)
|
|
381
|
+
unless process_activities(args, :set)
|
|
382
|
+
retval = 1
|
|
383
|
+
end
|
|
373
384
|
when 'show'
|
|
374
385
|
if args.empty?
|
|
375
386
|
@ffs.show_list_in_browser
|
|
@@ -378,12 +389,18 @@ EOT
|
|
|
378
389
|
# given day in a browser.
|
|
379
390
|
@ffs.show_monitoring(args[0])
|
|
380
391
|
else
|
|
381
|
-
process_activities(args, :show)
|
|
392
|
+
unless process_activities(args, :show)
|
|
393
|
+
retval = 1
|
|
394
|
+
end
|
|
382
395
|
end
|
|
383
396
|
when 'sources'
|
|
384
|
-
process_activities(args, :sources)
|
|
397
|
+
unless process_activities(args, :sources)
|
|
398
|
+
retval = 1
|
|
399
|
+
end
|
|
385
400
|
when 'summary'
|
|
386
|
-
process_activities(args, :summary)
|
|
401
|
+
unless process_activities(args, :summary)
|
|
402
|
+
retval = 1
|
|
403
|
+
end
|
|
387
404
|
when 'units'
|
|
388
405
|
change_unit_system(args)
|
|
389
406
|
when 'htmldir'
|
|
@@ -399,7 +416,7 @@ EOT
|
|
|
399
416
|
# Ensure that all updates are written to the database.
|
|
400
417
|
@db.sync
|
|
401
418
|
|
|
402
|
-
|
|
419
|
+
retval
|
|
403
420
|
end
|
|
404
421
|
|
|
405
422
|
def help
|
|
@@ -409,11 +426,13 @@ EOT
|
|
|
409
426
|
def process_files_or_activities(files_or_activities, command)
|
|
410
427
|
files_or_activities.each do |foa|
|
|
411
428
|
if foa[0] == ':'
|
|
412
|
-
process_activities([ foa ], command)
|
|
429
|
+
return false unless process_activities([ foa ], command)
|
|
413
430
|
else
|
|
414
|
-
process_files([ foa ], command)
|
|
431
|
+
return false unless process_files([ foa ], command)
|
|
415
432
|
end
|
|
416
433
|
end
|
|
434
|
+
|
|
435
|
+
true
|
|
417
436
|
end
|
|
418
437
|
|
|
419
438
|
def process_activities(activity_refs, command)
|
|
@@ -426,15 +445,19 @@ EOT
|
|
|
426
445
|
activities = @ffs.find(a_ref[1..-1])
|
|
427
446
|
if activities.empty?
|
|
428
447
|
Log.warn "No matching activities found for '#{a_ref}'"
|
|
429
|
-
return
|
|
448
|
+
return false
|
|
449
|
+
end
|
|
450
|
+
activities.each do |a|
|
|
451
|
+
unless process_activity(a, command)
|
|
452
|
+
return false
|
|
453
|
+
end
|
|
430
454
|
end
|
|
431
|
-
activities.each { |a| process_activity(a, command) }
|
|
432
455
|
else
|
|
433
456
|
Log.abort "Activity references must start with ':': #{a_ref}"
|
|
434
457
|
end
|
|
435
458
|
end
|
|
436
459
|
|
|
437
|
-
|
|
460
|
+
true
|
|
438
461
|
end
|
|
439
462
|
|
|
440
463
|
def process_files(files_or_dirs, command)
|
|
@@ -445,12 +468,14 @@ EOT
|
|
|
445
468
|
files_or_dirs.each do |fod|
|
|
446
469
|
if File.directory?(fod)
|
|
447
470
|
Dir.glob(File.join(fod, '*.FIT'), File::FNM_CASEFOLD).each do |file|
|
|
448
|
-
process_file(file, command)
|
|
471
|
+
return false unless process_file(file, command)
|
|
449
472
|
end
|
|
450
473
|
else
|
|
451
|
-
process_file(fod, command)
|
|
474
|
+
return false unless process_file(fod, command)
|
|
452
475
|
end
|
|
453
476
|
end
|
|
477
|
+
|
|
478
|
+
true
|
|
454
479
|
end
|
|
455
480
|
|
|
456
481
|
# Process a single FIT file according to the given command.
|
|
@@ -115,6 +115,10 @@ module PostRunner
|
|
|
115
115
|
t.localtime(utc_offset).strftime('%H:%M')
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
+
def prefix_for_time(t, a)
|
|
119
|
+
a.window_start_time.strftime('%Y-%m-%d') == t.strftime('%Y-%m-%d') ? ' ' : '+'
|
|
120
|
+
end
|
|
121
|
+
|
|
118
122
|
def daily_sleep_cycle_table(analyzer)
|
|
119
123
|
ti = FlexiTable.new
|
|
120
124
|
ti.head
|
|
@@ -129,8 +133,8 @@ module PostRunner
|
|
|
129
133
|
if last_to_time && c.from_time > last_to_time
|
|
130
134
|
# We have a gap in the sleep cycles.
|
|
131
135
|
ti.cell('Wake')
|
|
132
|
-
cell_right_aligned(ti, time_as_hm(last_to_time, utc_offset))
|
|
133
|
-
cell_right_aligned(ti, time_as_hm(c.from_time, utc_offset))
|
|
136
|
+
cell_right_aligned(ti, prefix_for_time(last_to_time, analyzer) + time_as_hm(last_to_time, utc_offset))
|
|
137
|
+
cell_right_aligned(ti, prefix_for_time(c.from_time, analyzer) + time_as_hm(c.from_time, utc_offset))
|
|
134
138
|
cell_right_aligned(ti, "(#{secsToHM(c.from_time - last_to_time)})")
|
|
135
139
|
ti.cell('')
|
|
136
140
|
ti.cell('')
|
|
@@ -139,8 +143,8 @@ module PostRunner
|
|
|
139
143
|
end
|
|
140
144
|
|
|
141
145
|
ti.cell((idx + 1).to_s, format)
|
|
142
|
-
ti.cell(c.from_time.localtime(utc_offset).strftime('%H:%M'), format)
|
|
143
|
-
ti.cell(c.to_time.localtime(utc_offset).strftime('%H:%M'), format)
|
|
146
|
+
ti.cell(prefix_for_time(c.from_time.localtime(utc_offset), analyzer) + c.from_time.localtime(utc_offset).strftime('%H:%M'), format)
|
|
147
|
+
ti.cell(prefix_for_time(c.to_time.localtime(utc_offset), analyzer) + c.to_time.localtime(utc_offset).strftime('%H:%M'), format)
|
|
144
148
|
|
|
145
149
|
duration = c.to_time - c.from_time
|
|
146
150
|
totals[:duration] += duration
|
|
@@ -111,7 +111,9 @@ module PostRunner
|
|
|
111
111
|
[ 'Longest Distance', '%.3f km' % (@distance / 1000.0), '-' ] :
|
|
112
112
|
[ PersonalRecords::SpeedRecordDistances[@sport][@distance],
|
|
113
113
|
secsToHMS(@duration),
|
|
114
|
-
|
|
114
|
+
@sport == 'running' ?
|
|
115
|
+
speedToPace(@distance / @duration) :
|
|
116
|
+
'%.1f' % (@distance / @duration * 3.6) ]) +
|
|
115
117
|
[ @store['file_store'].ref_by_activity(@activity),
|
|
116
118
|
ActivityLink.new(@activity, false),
|
|
117
119
|
@start_time.strftime("%Y-%m-%d") ])
|
|
@@ -220,7 +222,9 @@ module PostRunner
|
|
|
220
222
|
def generate_table
|
|
221
223
|
t = FlexiTable.new
|
|
222
224
|
t.head
|
|
223
|
-
t.row([ 'Record', 'Time/Dist.',
|
|
225
|
+
t.row([ 'Record', 'Time/Dist.',
|
|
226
|
+
@sport == 'running' ? 'Avg. Pace' : 'Avg. Speed',
|
|
227
|
+
'Ref.', 'Activity',
|
|
224
228
|
'Date' ],
|
|
225
229
|
{ :halign => :center })
|
|
226
230
|
t.set_column_attributes([
|