postrunner 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
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