postrunner 1.0.0 → 1.0.5
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 +66 -18
- data/Rakefile +7 -22
- data/lib/postrunner/Activity.rb +67 -0
- 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/FitFileStore.rb +18 -39
- data/lib/postrunner/Main.rb +41 -17
- data/lib/postrunner/MonitoringStatistics.rb +8 -4
- 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 +29 -31
- 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: a3573b66dc63e6e6e26ed20c4389a9c4da0ea2aa3a6bba64e5cce363ddbe1531
|
4
|
+
data.tar.gz: 26e721d0ddc3cae1852c7135de1f24d3dc03e32be871c0894c958591ec538f11
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eac508a3916d92e2843e3ff34a454fd357ad35dbcf798178e7bdb555ef9b25f9010d5f647567e1907982bf11adb24c71592d3e3e3af1b3fe46d9c921cb36ea31
|
7
|
+
data.tar.gz: 51994f58aefd6354123655260a45772cc4d7e301d236b76628d27ce2af5370297aecd3c669e2147f8bf0ae91ffcbd5c7c2fbd171bf4fa6eff0a4015f7705a2ef
|
@@ -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
@@ -2,45 +2,93 @@
|
|
2
2
|
|
3
3
|
PostRunner is an application to manage FIT files such as those
|
4
4
|
produced by Garmin products like the Forerunner 620 (FR620),
|
5
|
-
Forerunner 25 (FR25), Fenix 3, Fenix 3HR
|
6
|
-
import the files from the device and analyze the
|
7
|
-
the common features like plotting pace, heart
|
8
|
-
other captured values it also provides a heart
|
9
|
-
analysis. It can also update satellite orbit
|
10
|
-
the device to speed-up GPS fix times.
|
11
|
-
|
12
|
-
|
5
|
+
Forerunner 25 (FR25), Fenix 3, Fenix 3HR, Fenix 5, Fenix 5+ or Fenix6.
|
6
|
+
It allows you to import the files from the device and analyze the
|
7
|
+
data. In addition to the common features like plotting pace, heart
|
8
|
+
rates, elevation and other captured values it also provides a heart
|
9
|
+
rate variability (HRV) analysis. It can also update satellite orbit
|
10
|
+
prediction (EPO) data on the device to speed-up GPS fix times.
|
11
|
+
Unfortunately, the download mechanism for CPE files used by the
|
12
|
+
devices with GPS chipsets from Sony is still unknown and hence
|
13
|
+
unsupported. Postrunner is an offline alternative to Garmin Connect.
|
14
|
+
The software has been developed and tested on Linux but should work on
|
15
|
+
other operating systems as well.
|
13
16
|
|
14
17
|
## Installation
|
15
18
|
|
16
19
|
PostRunner is a [http://www.ruby-lang.org](Ruby) application. You need
|
17
|
-
to have a Ruby 2.
|
20
|
+
to have a Ruby 2.4 or later runtime environment installed. This
|
18
21
|
application was developed and tested on Linux but may work on other
|
19
|
-
operating systems as well.
|
22
|
+
operating systems as well. You can either install it as root for all
|
23
|
+
users on the computer or as a particular user for just this user.
|
24
|
+
|
25
|
+
### System-wide installation as root user
|
20
26
|
|
21
27
|
```
|
22
28
|
$ gem install postrunner
|
23
29
|
```
|
24
30
|
|
31
|
+
On some Linux distributions using sudo might resolve in permission
|
32
|
+
problems as the installed packages are not readable for normal users.
|
33
|
+
This typically results in 'cannot load such file' type error messages.
|
34
|
+
|
35
|
+
### Installation as non-privileged user
|
36
|
+
|
37
|
+
```
|
38
|
+
gem install --user-install postrunner
|
39
|
+
```
|
40
|
+
|
41
|
+
This will install PostRunner and all dependency packages in your .gem
|
42
|
+
directory. You then need to add the binary path to your PATH variable
|
43
|
+
in your .profile or .bashrc or .whatever file. The path is typically
|
44
|
+
.gem/ruby/<version>/bin. Watch out, on some Linux distributions the
|
45
|
+
version number of ruby gets added to the binary name, e. g.
|
46
|
+
postrunner.ruby2.7. You can use a symbolic link or alias to safe some
|
47
|
+
typing.
|
48
|
+
|
25
49
|
## Usage
|
26
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
|
+
|
27
68
|
### Importing FIT files
|
28
69
|
|
29
70
|
To get started you need to connect your device to your computer and
|
30
|
-
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
|
31
72
|
system are supported. Older devices use proprietary drivers and are
|
32
|
-
not supported by
|
73
|
+
not supported by PostRunner. Once the device is mounted find out the
|
33
74
|
full path to the directory that contains your FIT files. You can then
|
34
75
|
import all files on the device.
|
35
76
|
|
77
|
+
* for USB-FAT
|
36
78
|
```
|
37
79
|
$ postrunner import /run/media/$USER/GARMIN/GARMIN/ACTIVITY/
|
38
80
|
```
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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.
|
44
92
|
|
45
93
|
### Viewing FIT file data on the console
|
46
94
|
|
@@ -128,7 +176,7 @@ $ postrunner show :1
|
|
128
176
|
## Contributing
|
129
177
|
|
130
178
|
PostRunner is currently work in progress. It does some things I want
|
131
|
-
with files from my Garmin
|
179
|
+
with files from my Garmin devices. It's certainly possible to do more
|
132
180
|
things and support more devices. Patches are welcome!
|
133
181
|
|
134
182
|
1. Fork it ( https://github.com/scrapper/postrunner/fork )
|
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
|
|
@@ -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
|
@@ -144,7 +144,9 @@ module PostRunner
|
|
144
144
|
|
145
145
|
# Generate a String that uniquely identifies the device that generated
|
146
146
|
# the FIT file.
|
147
|
-
id = extract_fit_file_id(fit_entity)
|
147
|
+
unless (id = extract_fit_file_id(fit_entity))
|
148
|
+
return nil
|
149
|
+
end
|
148
150
|
long_uid = "#{id[:numeric_manufacturer]}-" +
|
149
151
|
"#{id[:numeric_product]}-#{id[:serial_number]}"
|
150
152
|
|
@@ -502,46 +504,23 @@ module PostRunner
|
|
502
504
|
return nil
|
503
505
|
end
|
504
506
|
|
505
|
-
|
506
|
-
|
507
|
-
#
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
:numeric_manufacturer => di.numeric_manufacturer,
|
518
|
-
:numeric_product => di.numeric_product,
|
519
|
-
:serial_number => di.serial_number
|
520
|
-
}
|
521
|
-
end
|
507
|
+
fit_entity.device_infos.each do |di|
|
508
|
+
# Not all FIT file have indexed device sections. In case the device
|
509
|
+
# index is nil we'll take the first entry.
|
510
|
+
if (di.device_index.nil? || di.device_index == 0) &&
|
511
|
+
di.numeric_manufacturer && di.numeric_product
|
512
|
+
return {
|
513
|
+
:manufacturer => di.manufacturer,
|
514
|
+
:product => di.garmin_product || di.product,
|
515
|
+
:numeric_manufacturer => di.numeric_manufacturer,
|
516
|
+
:numeric_product => di.numeric_product,
|
517
|
+
:serial_number => di.serial_number || 0
|
518
|
+
}
|
522
519
|
end
|
523
|
-
Log.error "Fit entity has no device info for 0"
|
524
|
-
return nil
|
525
|
-
else
|
526
|
-
# And for all properly developed devices we can just look at the
|
527
|
-
# file_id section.
|
528
|
-
if fid.manufacturer.nil? ||
|
529
|
-
fid.manufacturer[0..'Undocumented value'.length - 1] ==
|
530
|
-
'Undocumented value'
|
531
|
-
Log.error "Cannot store FIT files for unknown manufacturer " +
|
532
|
-
fid.manufacturer
|
533
|
-
return nil
|
534
|
-
end
|
535
|
-
fid.serial_number ||= 0
|
536
|
-
|
537
|
-
return {
|
538
|
-
:manufacturer => fid.manufacturer,
|
539
|
-
:product => fid.garmin_product || fid.product,
|
540
|
-
:numeric_manufacturer => di.numeric_manufacturer,
|
541
|
-
:numeric_product => di.numeric_product,
|
542
|
-
:serial_number => fid.serial_number
|
543
|
-
}
|
544
520
|
end
|
521
|
+
|
522
|
+
Log.error "Fit entity has no device info section"
|
523
|
+
return nil
|
545
524
|
end
|
546
525
|
|
547
526
|
def register_device(long_uid)
|
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.
|
@@ -597,7 +622,6 @@ EOT
|
|
597
622
|
|
598
623
|
def handle_version_update
|
599
624
|
if @db['config']['version'] != VERSION
|
600
|
-
puts "Work needed"
|
601
625
|
from_version = Gem::Version.new(@db['config']['version'])
|
602
626
|
to_version = Gem::Version.new(VERSION)
|
603
627
|
|