postrunner 0.0.9 → 0.0.10

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 19aaff0f7f7586b68eb837bd1679ce7e31b9cfe7
4
- data.tar.gz: a25b3ceb7a05e129edc6560d73ccb65a1ebe0e16
3
+ metadata.gz: 4dd09a8e49b1feea3d264427dae30981cb842f22
4
+ data.tar.gz: e9ec20c4cf98828275ab8f82d1230101dd35af53
5
5
  SHA512:
6
- metadata.gz: e0abfc8cef81ebf908bbcedb7443eb2481537c0f4156502aa65e6730c2b58607117a0c0d58886907b042c049e38ec8043bc3c632e4fc2b3e2a965409a81e7962
7
- data.tar.gz: 773ba376b663e6b8aaddbbbb3e84e968b9b57d1d5c5cb0b39d5e809725268afdcf9a667b5647f1e325bdce399198f58d575d1884725c71bac4f27bc240a6e57f
6
+ metadata.gz: abe3df33c0cc4ae5bf687a365368cc1b969d9b754776097bccc70d134aed86ab6e933896579557a51a1ea3cd59eb7a57b4da861abf2d822e4d865474e3253978
7
+ data.tar.gz: 97f348e3b2f6faa5ebda4b7cce5517f7f1bf9dafa5d75b006e875a8490dca8963570c2042428783e99ef52b49d1b17b419f372e32c6fe627fbbb7e5872366b8b
data/Rakefile CHANGED
@@ -1,16 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
3
 
4
- # Add the include path for the fit4ruby library. We assume it is located in
5
- # the same directory as the postrunner directory.
6
- fit4ruby = File.realpath(File.join(File.dirname(__FILE__), '..',
7
- 'fit4ruby', 'lib'))
8
- if ENV['RUBYLIB']
9
- ENV['RUBYLIB'] += ":#{fit4ruby}"
10
- else
11
- ENV['RUBYLIB'] = fit4ruby
12
- end
13
-
14
4
  RSpec::Core::RakeTask.new
15
5
 
16
6
  task :default => :spec
@@ -10,7 +10,11 @@
10
10
  # published by the Free Software Foundation.
11
11
  #
12
12
 
13
- $:.unshift(File.join(File.dirname(__FILE__), '..', '..', 'fit4ruby', 'lib'))
13
+ # Some dependencies may not be installed as Ruby Gems but as local sources.
14
+ # Add them to the LOAD_PATH.
15
+ %w( fit4ruby perobs ).each do |lib_dir|
16
+ $:.unshift(File.join(File.dirname(__FILE__), '..', '..', lib_dir, 'lib'))
17
+ end
14
18
  $:.unshift(File.dirname(__FILE__))
15
19
 
16
20
  require 'postrunner/Main'
@@ -89,36 +89,26 @@ module PostRunner
89
89
  # @param fit_file [String] Name of the FIT file.
90
90
  # @return [TrueClass or FalseClass] True if the file could be added. False
91
91
  # otherwise.
92
- def add(fit_file)
93
- base_fit_file = File.basename(fit_file)
94
- if @activities.find { |a| a.fit_file == base_fit_file }
95
- Log.debug "Activity #{fit_file} is already included in the archive"
96
- return false
97
- end
92
+ def add(fit_file_name, fit_activity)
93
+ base_fit_file_name = File.basename(fit_file_name)
98
94
 
99
- if File.exists?(File.join(@fit_dir, base_fit_file))
100
- Log.debug "Activity #{fit_file} has been deleted before"
95
+ if @activities.find { |a| a.fit_file == base_fit_file_name }
96
+ Log.debug "Activity #{fit_file_name} is already included in the archive"
101
97
  return false
102
98
  end
103
99
 
104
- begin
105
- fit_activity = Fit4Ruby.read(fit_file)
106
- rescue Fit4Ruby::Error
107
- Log.error $!
108
- return false
109
- end
110
- unless fit_activity
111
- Log.error "#{fit_file} does not contain any activity records"
100
+ if File.exists?(File.join(@fit_dir, base_fit_file_name))
101
+ Log.debug "Activity #{fit_file_name} has been deleted before"
112
102
  return false
113
103
  end
114
104
 
115
105
  begin
116
- FileUtils.cp(fit_file, @fit_dir)
106
+ FileUtils.cp(fit_file_name, @fit_dir)
117
107
  rescue StandardError
118
- Log.fatal "Cannot copy #{fit_file} into #{@fit_dir}: #{$!}"
108
+ Log.fatal "Cannot copy #{fit_file_name} into #{@fit_dir}: #{$!}"
119
109
  end
120
110
 
121
- @activities << (activity = Activity.new(self, base_fit_file,
111
+ @activities << (activity = Activity.new(self, base_fit_file_name,
122
112
  fit_activity))
123
113
  @activities.sort! do |a1, a2|
124
114
  a2.timestamp <=> a1.timestamp
@@ -140,7 +130,7 @@ module PostRunner
140
130
  end
141
131
 
142
132
  sync
143
- Log.info "#{fit_file} successfully added to archive"
133
+ Log.info "#{fit_file_name} successfully added to archive"
144
134
 
145
135
  true
146
136
  end
@@ -54,6 +54,11 @@ module PostRunner
54
54
  t.row([ 'Distance:',
55
55
  local_value(session, 'total_distance', '%.2f %s',
56
56
  { :metric => 'km', :statute => 'mi'}) ])
57
+ if session.has_geo_data?
58
+ t.row([ 'GPS Data based Distance:',
59
+ local_value(@fit_activity, 'total_gps_distance', '%.2f %s',
60
+ { :metric => 'km', :statute => 'mi'}) ])
61
+ end
57
62
  t.row([ 'Time:', secsToHMS(session.total_timer_time) ])
58
63
  if @activity.sport == 'running' || @activity.sport == 'multisport'
59
64
  t.row([ 'Avg. Pace:', pace(session, 'avg_speed') ])
@@ -1,4 +1,5 @@
1
- #!/usr/bin/env ruby -w # encoding: UTF-8
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
2
3
  #
3
4
  # = ChartView.rb -- PostRunner - Manage the data from your Garmin sport devices.
4
5
  #
@@ -176,8 +177,9 @@ EOT
176
177
  else
177
178
  value = (value * 3600.0 * 1000).to_i
178
179
  end
180
+ min_value = 0.0
179
181
  else
180
- min_value = value if value && (min_value.nil? || min_value > value)
182
+ min_value = value if (min_value.nil? || min_value > value)
181
183
  end
182
184
  data_set << [ ((r.timestamp.to_i - start_time) * 1000).to_i, value ]
183
185
  end
@@ -190,18 +192,19 @@ EOT
190
192
  s << data_set.map do |set|
191
193
  "[ #{set[0]}, #{set[1] ? set[1] : 'null'} ]"
192
194
  end.join(', ')
195
+ s << "];\n"
196
+
197
+ s << lap_marks(start_time)
193
198
 
194
199
  chart_id = "#{field}_chart"
195
200
  s << <<"EOT"
196
- ];
197
-
198
- $.plot(\"##{chart_id}\",
201
+ var plot = $.plot(\"##{chart_id}\",
199
202
  [ { data: #{field}_data,
200
203
  #{color ? "color: \"#{color}\"," : ''}
201
204
  lines: { show: true#{field == 'pace' ? '' :
202
205
  ', fill: true'} } } ],
203
206
  { xaxis: { mode: "time" },
204
- grid: { hoverable: true }
207
+ grid: { markings: lap_marks, hoverable: true }
205
208
  EOT
206
209
  if field == 'pace'
207
210
  s << ", yaxis: { mode: \"time\",\n" +
@@ -212,6 +215,7 @@ EOT
212
215
  s << ", yaxis: { min: #{0.9 * min_value} }"
213
216
  end
214
217
  s << "});\n"
218
+ s << lap_mark_labels(chart_id, start_time)
215
219
  s << hover_function(chart_id, y_label, select_unit(unit)) + "\n"
216
220
  end
217
221
 
@@ -233,11 +237,11 @@ EOT
233
237
  # Find the right set by looking at the maximum allowed values for each
234
238
  # color.
235
239
  colors.each.with_index do |col_max_value, i|
236
- col, max_value = col_max_value
237
- if max_value.nil? || value < max_value
238
- # A max_value of nil means all values allowed. The value is in the
239
- # allowed range for this set, so add the value as x/y pair to the
240
- # set.
240
+ col, range_max_value = col_max_value
241
+ if range_max_value.nil? || value < range_max_value
242
+ # A range_max_value of nil means all values allowed. The value is
243
+ # in the allowed range for this set, so add the value as x/y pair
244
+ # to the set.
241
245
  x_val = (r.timestamp.to_i - start_time) * 1000
242
246
  data_sets[i] << [ x_val, r.get_as(field, select_unit(unit)) ]
243
247
  # Abort the color loop since we've found the right set already.
@@ -260,8 +264,10 @@ EOT
260
264
  s << " ];\n"
261
265
  end
262
266
 
267
+ s << lap_marks(start_time)
268
+
263
269
  chart_id = "#{field}_chart"
264
- s << "$.plot(\"##{chart_id}\", [\n"
270
+ s << "var plot = $.plot(\"##{chart_id}\", [\n"
265
271
  s << data_sets.map do |index, ds|
266
272
  "{ data: #{field}_data_#{index},\n" +
267
273
  " color: \"#{colors[index][0]}\",\n" +
@@ -269,7 +275,8 @@ EOT
269
275
  " fill: true, radius: 2 } }"
270
276
  end.join(', ')
271
277
  s << "], { xaxis: { mode: \"time\" },
272
- grid: { hoverable: true } });\n"
278
+ grid: { markings: lap_marks, hoverable: true } });\n"
279
+ s << lap_mark_labels(chart_id, start_time)
273
280
  s << hover_function(chart_id, y_label, select_unit(unit))
274
281
 
275
282
  s
@@ -302,6 +309,34 @@ EOT
302
309
  EOT
303
310
  end
304
311
 
312
+ def lap_marks(start_time)
313
+ # Use vertical lines to mark the end of each lap.
314
+ s = "var lap_marks = [\n"
315
+ s += @activity.fit_activity.laps.map do |lap|
316
+ x = ((lap.timestamp.to_i - start_time) * 1000).to_i
317
+ "\n { color: \"#666\", lineWidth: 1, " +
318
+ "xaxis: { from: #{x} , to: #{x} } }"
319
+ end.join(",\n")
320
+ s + "];\n"
321
+ end
322
+
323
+ def lap_mark_labels(chart_id, start_time)
324
+ # Mark the vertical lap marks with the number of the lap right to the
325
+ # left at the top end of the line.
326
+ s = ''
327
+ @activity.fit_activity.laps[0..-2].each do |lap|
328
+ x = ((lap.timestamp.to_i - start_time) * 1000).to_i
329
+ s += "$(\"##{chart_id}\").append(" +
330
+ "\"<div style='position:absolute;" +
331
+ "left:\" + (plot.pointOffset({x: #{x}, y: 0}).left - 18) + \"px;" +
332
+ "top:7px;width:16px;" +
333
+ "text-align:right;" +
334
+ "color:#666;font-size:smaller'>" +
335
+ "#{lap.message_index + 1}</div>\");\n"
336
+ end
337
+ s
338
+ end
339
+
305
340
  end
306
341
 
307
342
  end
@@ -66,6 +66,8 @@ module PostRunner
66
66
  end
67
67
  if (type = device.device_type)
68
68
  rename = { 'heart_rate' => 'Heart Rate Sensor',
69
+ 'barometric_pressure' => 'Barometer',
70
+ 'position' => 'GPS',
69
71
  'stride_speed_distance' => 'Footpod',
70
72
  'running_dynamics' => 'Running Dynamics' }
71
73
  type = rename[type] if rename.include?(type)
@@ -13,10 +13,12 @@
13
13
  require 'optparse'
14
14
  require 'logger'
15
15
  require 'fit4ruby'
16
+ require 'perobs'
16
17
 
17
18
  require 'postrunner/version'
18
19
  require 'postrunner/RuntimeConfig'
19
20
  require 'postrunner/ActivitiesDB'
21
+ require 'postrunner/MonitoringDB'
20
22
  require 'postrunner/EPO_Downloader'
21
23
 
22
24
  module PostRunner
@@ -36,11 +38,13 @@ module PostRunner
36
38
  @attribute = nil
37
39
  @value = nil
38
40
  @activities = nil
41
+ @monitoring = nil
39
42
  @db_dir = File.join(ENV['HOME'], '.postrunner')
40
43
 
41
44
  return if (args = parse_options(args)).nil?
42
45
 
43
46
  @cfg = RuntimeConfig.new(@db_dir)
47
+ @db = PEROBS::Store.new(File.join(@db_dir, 'database'))
44
48
  execute_command(args)
45
49
  end
46
50
 
@@ -188,6 +192,7 @@ EOT
188
192
 
189
193
  def execute_command(args)
190
194
  @activities = ActivitiesDB.new(@db_dir, @cfg)
195
+ @monitoring = MonitoringDB.new(@db, @cfg)
191
196
  handle_version_update
192
197
 
193
198
  case (cmd = args.shift)
@@ -301,17 +306,44 @@ EOT
301
306
  end
302
307
  end
303
308
 
309
+ # Process a single FIT file according to the given command.
310
+ # @param file [String] File name of a FIT file
311
+ # @param command [Symbol] Processing instruction
312
+ # @return [TrueClass, FalseClass] true if command was successful, false
313
+ # otherwise
304
314
  def process_file(file, command)
305
315
  case command
306
316
  when :check, :dump
307
317
  read_fit_file(file)
308
318
  when :import
309
- @activities.add(file)
319
+ import_fit_file(file)
310
320
  else
311
321
  Log.fatal("Unknown file command #{command}")
312
322
  end
313
323
  end
314
324
 
325
+ # Import the given FIT file.
326
+ # @param fit_file_name [String] File name of the FIT file
327
+ # @return [TrueClass, FalseClass] true if file was successfully imported,
328
+ # false otherwise
329
+ def import_fit_file(fit_file_name)
330
+ begin
331
+ fit_entity = Fit4Ruby.read(fit_file_name)
332
+ rescue Fit4Ruby::Error
333
+ Log.error $!
334
+ return false
335
+ end
336
+
337
+ if fit_entity.is_a?(Fit4Ruby::Activity)
338
+ return @activities.add(fit_file_name, fit_entity)
339
+ elsif fit_entity.is_a?(Fit4Ruby::Monitoring_B)
340
+ return @monitoring.add(fit_file_name, fit_entity)
341
+ else
342
+ Log.error "#{fit_file_name} is not a recognized FIT file"
343
+ return false
344
+ end
345
+ end
346
+
315
347
  def process_activity(activity, command)
316
348
  case command
317
349
  when :check
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = MonitoringDB.rb -- PostRunner - Manage the data from your Garmin sport devices.
5
+ #
6
+ # Copyright (c) 2014, 2015 by Chris Schlaeger <cs@taskjuggler.org>
7
+ #
8
+ # This program is free software; you can redistribute it and/or modify
9
+ # it under the terms of version 2 of the GNU General Public License as
10
+ # published by the Free Software Foundation.
11
+ #
12
+
13
+ module PostRunner
14
+
15
+ class MonitoringDB
16
+
17
+ def initialize(store, cfg)
18
+ @store = store
19
+ end
20
+
21
+ def add(fit_file_name, fit_monitoring_b)
22
+ start_time = fit_monitoring_b.monitoring_infos[0].timestamp
23
+
24
+ fit_monitoring_b.monitorings.each do |monitoring|
25
+ if (cati = monitoring.current_activity_type_intensity)
26
+ data.activity_type = decode_activity_type(cati & 0x1F)
27
+ data.intensity = (cati >> 5) & 0x7
28
+ #puts "#{monitoring.timestamp}: #{decode_activity_type(cati & 0x1F)}" +
29
+ # " #{(cati >> 5) & 0x7}"
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def decode_activity_type(activity_type)
37
+ types = [ :generic, :running, :cycling, :transition,
38
+ :fitness_equipment, :swimming, :walking, :unknown7,
39
+ :resting, :unknown9 ]
40
+ if (decoded_type = types[activity_type])
41
+ decoded_type
42
+ else
43
+ Log.error "Unknown activity type #{activity_type}"
44
+ :generic
45
+ end
46
+ end
47
+
48
+ end
49
+
50
+ end
@@ -23,6 +23,7 @@ module PostRunner
23
23
  # Create a new RC object.
24
24
  # @param dir [String] the directory to hold the config.yml file.
25
25
  def initialize(dir)
26
+ create_directory(dir, 'application data')
26
27
  @options = {
27
28
  :version => '0.0.0',
28
29
  :unit_system => :metric,
@@ -11,5 +11,5 @@
11
11
  #
12
12
 
13
13
  module PostRunner
14
- VERSION = "0.0.9"
14
+ VERSION = "0.0.10"
15
15
  end
@@ -19,7 +19,8 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
  spec.required_ruby_version = '>=2.0'
21
21
 
22
- spec.add_dependency 'fit4ruby', '~> 0.0.6'
22
+ spec.add_dependency 'fit4ruby', '~> 0.0.7'
23
+ spec.add_dependency 'perobs', '~> 1.0.0'
23
24
  spec.add_dependency 'nokogiri', '~> 1.6'
24
25
 
25
26
  spec.add_development_dependency 'bundler', '~> 1.6'
@@ -10,8 +10,8 @@
10
10
  # published by the Free Software Foundation.
11
11
  #
12
12
 
13
- require 'postrunner/ActivitySummary'
14
13
  require 'spec_helper'
14
+ require 'postrunner/ActivitySummary'
15
15
 
16
16
  class Activity < Struct.new(:fit_activity, :sport)
17
17
  end
@@ -10,6 +10,7 @@
10
10
  # published by the Free Software Foundation.
11
11
  #
12
12
 
13
+ require 'spec_helper'
13
14
  require 'postrunner/FlexiTable'
14
15
 
15
16
  describe PostRunner::FlexiTable do
@@ -12,8 +12,8 @@
12
12
 
13
13
  require 'fileutils'
14
14
 
15
- require 'postrunner/Main'
16
15
  require 'spec_helper'
16
+ require 'postrunner/Main'
17
17
 
18
18
  describe PostRunner::Main do
19
19
 
@@ -38,6 +38,7 @@ describe PostRunner::Main do
38
38
  FileUtils.rm_rf(@db_dir)
39
39
  FileUtils.rm_rf('FILE1.FIT')
40
40
  FileUtils.rm_rf('FILE2.FIT')
41
+ FileUtils::rm_rf('icons')
41
42
  end
42
43
 
43
44
  it 'should abort without arguments' do
@@ -10,6 +10,7 @@
10
10
  # published by the Free Software Foundation.
11
11
  #
12
12
 
13
+ require 'spec_helper'
13
14
  require 'postrunner/View'
14
15
  require 'postrunner/ViewButtons'
15
16
  require 'postrunner/PagingButtons'
@@ -10,6 +10,12 @@
10
10
  # published by the Free Software Foundation.
11
11
  #
12
12
 
13
+ # Some dependencies may not be installed as Ruby Gems but as local sources.
14
+ # Add them and the postrunner dir to the LOAD_PATH.
15
+ %w( postrunner fit4ruby perobs ).each do |lib_dir|
16
+ $:.unshift(File.join(File.dirname(__FILE__), '..', '..', lib_dir, 'lib'))
17
+ end
18
+
13
19
  def create_fit_file(name, date, duration_minutes = 30)
14
20
  Fit4Ruby.write(name, create_fit_activity(date, duration_minutes))
15
21
  end
@@ -29,6 +35,7 @@ def create_fit_activity(date, duration_minutes)
29
35
  :device_index => 0 })
30
36
  a.new_device_info({ :timestamp => ts, :manufacturer => 'garmin',
31
37
  :device_index => 1, :battery_status => 'ok' })
38
+ laps = 0
32
39
  0.upto((a.total_timer_time / 60) - 1) do |mins|
33
40
  a.new_record({
34
41
  :timestamp => ts,
@@ -47,7 +54,9 @@ def create_fit_activity(date, duration_minutes)
47
54
  })
48
55
 
49
56
  if mins > 0 && mins % 5 == 0
50
- a.new_lap({ :timestamp => ts, :sport => 'running' })
57
+ a.new_lap({ :timestamp => ts, :sport => 'running',
58
+ :message_index => laps })
59
+ laps += 1
51
60
  end
52
61
  ts += 60
53
62
  end
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.0.9
4
+ version: 0.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Schlaeger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-09-11 00:00:00.000000000 Z
11
+ date: 2015-10-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fit4ruby
@@ -16,14 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - ~>
18
18
  - !ruby/object:Gem::Version
19
- version: 0.0.6
19
+ version: 0.0.7
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: 0.0.6
26
+ version: 0.0.7
27
+ - !ruby/object:Gem::Dependency
28
+ name: perobs
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 1.0.0
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: nokogiri
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -125,6 +139,7 @@ files:
125
139
  - lib/postrunner/FlexiTable.rb
126
140
  - lib/postrunner/HTMLBuilder.rb
127
141
  - lib/postrunner/Main.rb
142
+ - lib/postrunner/MonitoringDB.rb
128
143
  - lib/postrunner/NavButtonRow.rb
129
144
  - lib/postrunner/PagingButtons.rb
130
145
  - lib/postrunner/PersonalRecords.rb