From 3c32e378c9ffc01c1ed994483571193127a4e6a4 Mon Sep 17 00:00:00 2001 From: Chris Schlaeger Date: Thu, 28 Aug 2014 20:23:38 +0200 Subject: Adding support for metric and statute units. --- lib/postrunner/ActivitiesDB.rb | 12 ++++ lib/postrunner/Activity.rb | 10 +-- lib/postrunner/ActivityListView.rb | 27 ++++++- lib/postrunner/ActivityReport.rb | 114 ------------------------------ lib/postrunner/ActivitySummary.rb | 141 +++++++++++++++++++++++++++++++++++++ lib/postrunner/ActivityView.rb | 10 +-- lib/postrunner/ChartView.rb | 57 +++++++++------ lib/postrunner/Main.rb | 16 +++++ lib/postrunner/RuntimeConfig.rb | 7 ++ spec/ActivitySummary_spec.rb | 28 ++++++++ spec/FlexiTable_spec.rb | 8 ++- spec/PostRunner_spec.rb | 65 +++-------------- spec/spec_helper.rb | 62 ++++++++++++++++ 13 files changed, 352 insertions(+), 205 deletions(-) delete mode 100644 lib/postrunner/ActivityReport.rb create mode 100644 lib/postrunner/ActivitySummary.rb create mode 100644 spec/ActivitySummary_spec.rb create mode 100644 spec/spec_helper.rb diff --git a/lib/postrunner/ActivitiesDB.rb b/lib/postrunner/ActivitiesDB.rb index 0e21e65..81f039a 100644 --- a/lib/postrunner/ActivitiesDB.rb +++ b/lib/postrunner/ActivitiesDB.rb @@ -248,6 +248,18 @@ module PostRunner end end + # This method can be called to re-generate all HTML reports and all HTML + # index files. + def generate_all_html_reports + Log.info "Re-generating all HTML report files..." + # Generate HTML views for all activities in the DB. + @activities.each { |a| a.generate_html_view } + Log.info "All HTML report files have been re-generated." + # (Re-)generate index files. + ActivityListView.new(self).update_html_index + Log.info "HTML index files have been updated." + end + private def sync diff --git a/lib/postrunner/Activity.rb b/lib/postrunner/Activity.rb index 70ab1ca..de4396b 100644 --- a/lib/postrunner/Activity.rb +++ b/lib/postrunner/Activity.rb @@ -12,7 +12,7 @@ require 'fit4ruby' -require 'postrunner/ActivityReport' +require 'postrunner/ActivitySummary' require 'postrunner/ActivityView' module PostRunner @@ -48,8 +48,7 @@ module PostRunner end def check - # Re-generate the HTML file for this activity - generate_html_view + @fit_activity = load_fit_file Log.info "FIT file #{@fit_file} is OK" end @@ -88,7 +87,7 @@ module PostRunner def summary @fit_activity = load_fit_file unless @fit_activity - puts ActivityReport.new(self).to_s + puts ActivitySummary.new(@fit_activity, name, @db.cfg[:unit_system]).to_s end def rename(name) @@ -110,7 +109,8 @@ module PostRunner def generate_html_view @fit_activity = load_fit_file unless @fit_activity - ActivityView.new(self, @db.predecessor(self), @db.successor(self)) + ActivityView.new(self, @db.cfg[:unit_system], @db.predecessor(self), + @db.successor(self)) end private diff --git a/lib/postrunner/ActivityListView.rb b/lib/postrunner/ActivityListView.rb index 5a37c26..61275da 100644 --- a/lib/postrunner/ActivityListView.rb +++ b/lib/postrunner/ActivityListView.rb @@ -42,6 +42,7 @@ module PostRunner def initialize(db) @db = db + @unit_system = @db.cfg[:unit_system] @page_size = 20 @page_no = -1 @last_page = (@db.activities.length - 1) / @page_size @@ -130,7 +131,7 @@ EOT end def generate_table - i = @page_no * @page_size + i = @page_no < 0 ? 0 : @page_no * @page_size t = FlexiTable.new t.head t.row(%w( Ref. Activity Start Distance Duration Pace ), @@ -151,9 +152,10 @@ EOT i += 1, ActivityLink.new(a), a.timestamp.strftime("%a, %Y %b %d %H:%M"), - "%.2f" % (a.total_distance / 1000), + local_value(a.total_distance, 'm', '%.2f', + { :metric => 'km', :statute => 'mi' }), secsToHMS(a.total_timer_time), - speedToPace(a.avg_speed) ]) + pace(a.avg_speed) ]) end t @@ -168,6 +170,25 @@ EOT Log.fatal "Cannot write activity index file '#{output_file}: #{$!}" end end + + def local_value(value, from_unit, format, units) + to_unit = units[@unit_system] + return '-' unless value + value *= conversion_factor(from_unit, to_unit) + "#{format % [value, to_unit]}" + end + + def pace(speed) + case @unit_system + when :metric + "#{speedToPace(speed)}" + when :statute + "#{speedToPace(speed, 1609.34)}" + else + Log.fatal "Unknown unit system #{@unit_system}" + end + end + end end diff --git a/lib/postrunner/ActivityReport.rb b/lib/postrunner/ActivityReport.rb deleted file mode 100644 index f15ed91..0000000 --- a/lib/postrunner/ActivityReport.rb +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env ruby -w -# encoding: UTF-8 -# -# = ActivityReport.rb -- PostRunner - Manage the data from your Garmin sport devices. -# -# Copyright (c) 2014 by Chris Schlaeger -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. -# - -require 'fit4ruby' - -require 'postrunner/FlexiTable' -require 'postrunner/ViewWidgets' - -module PostRunner - - class ActivityReport - - include Fit4Ruby::Converters - include ViewWidgets - - def initialize(activity) - @activity = activity - end - - def to_s - session = @activity.fit_activity.sessions[0] - - summary(session).to_s + "\n" + laps.to_s - end - - def to_html(doc) - session = @activity.fit_activity.sessions[0] - - frame(doc, "Activity: #{@activity.name}") { - summary(session).to_html(doc) - } - frame(doc, 'Laps') { - laps.to_html(doc) - } - end - - private - - def summary(session) - t = FlexiTable.new - t.enable_frame(false) - t.body - t.row([ 'Date:', session.timestamp]) - t.row([ 'Distance:', "#{'%.2f' % (session.total_distance / 1000.0)} km" ]) - t.row([ 'Time:', secsToHMS(session.total_timer_time) ]) - t.row([ 'Avg. Pace:', - "#{speedToPace(session.avg_speed)} min/km" ]) - t.row([ 'Total Ascend:', "#{session.total_ascend} m" ]) - t.row([ 'Total Descend:', "#{session.total_descent} m" ]) - t.row([ 'Calories:', "#{session.total_calories} kCal" ]) - t.row([ 'Avg. HR:', session.avg_heart_rate ? - "#{session.avg_heart_rate} bpm" : '-' ]) - t.row([ 'Max. HR:', session.max_heart_rate ? - "#{session.max_heart_rate} bpm" : '-' ]) - t.row([ 'Training Effect:', session.total_training_effect ? - session.total_training_effect : '-' ]) - t.row([ 'Avg. Run Cadence:', - session.avg_running_cadence ? - "#{session.avg_running_cadence.round} spm" : '-' ]) - t.row([ 'Avg. Vertical Oscillation:', - session.avg_vertical_oscillation ? - "#{'%.1f' % (session.avg_vertical_oscillation / 10)} cm" : '-' ]) - t.row([ 'Avg. Ground Contact Time:', - session.avg_stance_time ? - "#{session.avg_stance_time.round} ms" : '-' ]) - t.row([ 'Avg. Stride Length:', - session.avg_stride_length ? - "#{'%.2f' % (session.avg_stride_length / 2)} m" : '-' ]) - rec_time = @activity.fit_activity.recovery_time - t.row([ 'Recovery Time:', rec_time ? secsToHMS(rec_time * 60) : '-' ]) - vo2max = @activity.fit_activity.vo2max - t.row([ 'VO2max:', vo2max ? vo2max : '-' ]) - - t - end - - def laps - t = FlexiTable.new - t.head - t.row([ 'Lap', 'Duration', 'Distance', 'Avg. Pace', 'Stride', 'Cadence', - 'Avg. HR', 'Max. HR' ]) - t.set_column_attributes(Array.new(8, { :halign => :right })) - t.body - @activity.fit_activity.sessions[0].laps.each.with_index do |lap, index| - t.cell(index + 1) - t.cell(secsToHMS(lap.total_timer_time)) - t.cell('%.2f' % (lap.total_distance / 1000.0)) - t.cell(speedToPace(lap.avg_speed)) - t.cell(lap.total_strides ? - '%.2f' % (lap.total_distance / (2 * lap.total_strides)) : '') - t.cell(lap.avg_running_cadence && lap.avg_fractional_cadence ? - '%.1f' % (2 * lap.avg_running_cadence + - (2 * lap.avg_fractional_cadence) / 100.0) : '') - t.cell(lap.avg_heart_rate.to_s) - t.cell(lap.max_heart_rate.to_s) - t.new_row - end - - t - end - - end - -end - diff --git a/lib/postrunner/ActivitySummary.rb b/lib/postrunner/ActivitySummary.rb new file mode 100644 index 0000000..c1f5771 --- /dev/null +++ b/lib/postrunner/ActivitySummary.rb @@ -0,0 +1,141 @@ +#!/usr/bin/env ruby -w +# encoding: UTF-8 +# +# = ActivitySummary.rb -- PostRunner - Manage the data from your Garmin sport devices. +# +# Copyright (c) 2014 by Chris Schlaeger +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# + +require 'fit4ruby' + +require 'postrunner/FlexiTable' +require 'postrunner/ViewWidgets' + +module PostRunner + + class ActivitySummary + + include Fit4Ruby::Converters + include ViewWidgets + + def initialize(fit_activity, name, unit_system) + @fit_activity = fit_activity + @name = name + @unit_system = unit_system + end + + def to_s + session = @fit_activity.sessions[0] + + summary(session).to_s + "\n" + laps.to_s + end + + def to_html(doc) + session = @fit_activity.sessions[0] + + frame(doc, "Activity: #{@name}") { + summary(session).to_html(doc) + } + frame(doc, 'Laps') { + laps.to_html(doc) + } + end + + private + + def summary(session) + t = FlexiTable.new + t.enable_frame(false) + t.body + t.row([ 'Date:', session.timestamp]) + t.row([ 'Distance:', + local_value(session, 'total_distance', '%.2f %s', + { :metric => 'km', :statute => 'mi'}) ]) + t.row([ 'Time:', secsToHMS(session.total_timer_time) ]) + t.row([ 'Avg. Pace:', pace(session, 'avg_speed') ]) + t.row([ 'Total Ascent:', + local_value(session, 'total_ascent', '%.0f %s', + { :metric => 'm', :statute => 'ft' }) ]) + t.row([ 'Total Descent:', + local_value(session, 'total_descent', '%.0f %s', + { :metric => 'm', :statute => 'ft' }) ]) + t.row([ 'Calories:', "#{session.total_calories} kCal" ]) + t.row([ 'Avg. HR:', session.avg_heart_rate ? + "#{session.avg_heart_rate} bpm" : '-' ]) + t.row([ 'Max. HR:', session.max_heart_rate ? + "#{session.max_heart_rate} bpm" : '-' ]) + t.row([ 'Training Effect:', session.total_training_effect ? + session.total_training_effect : '-' ]) + t.row([ 'Avg. Run Cadence:', + session.avg_running_cadence ? + "#{session.avg_running_cadence.round} spm" : '-' ]) + t.row([ 'Avg. Vertical Oscillation:', + local_value(session, 'avg_vertical_oscillation', '%.1f %s', + { :metric => 'cm', :statute => 'in' }) ]) + t.row([ 'Avg. Ground Contact Time:', + session.avg_stance_time ? + "#{session.avg_stance_time.round} ms" : '-' ]) + t.row([ 'Avg. Stride Length:', + local_value(session, 'avg_stride_length', '%.2f %s', + { :metric => 'm', :statute => 'ft' }) ]) + rec_time = @fit_activity.recovery_time + t.row([ 'Recovery Time:', rec_time ? secsToHMS(rec_time * 60) : '-' ]) + vo2max = @fit_activity.vo2max + t.row([ 'VO2max:', vo2max ? vo2max : '-' ]) + + t + end + + def laps + t = FlexiTable.new + t.head + t.row([ 'Lap', 'Duration', 'Distance', 'Avg. Pace', 'Stride', 'Cadence', + 'Avg. HR', 'Max. HR' ]) + t.set_column_attributes(Array.new(8, { :halign => :right })) + t.body + @fit_activity.sessions[0].laps.each.with_index do |lap, index| + t.cell(index + 1) + t.cell(secsToHMS(lap.total_timer_time)) + t.cell(local_value(lap, 'total_distance', '%.2f', + { :metric => 'km', :statute => 'mi' })) + t.cell(pace(lap, 'avg_speed', false)) + t.cell(local_value(lap, 'avg_stride_length', '%.2f', + { :metric => 'm', :statute => 'ft' })) + t.cell(lap.avg_running_cadence && lap.avg_fractional_cadence ? + '%.1f' % (2 * lap.avg_running_cadence + + (2 * lap.avg_fractional_cadence) / 100.0) : '') + t.cell(lap.avg_heart_rate.to_s) + t.cell(lap.max_heart_rate.to_s) + t.new_row + end + + t + end + + def local_value(fdr, field, format, units) + unit = units[@unit_system] + value = fdr.get_as(field, unit) + return '-' unless value + "#{format % [value, unit]}" + end + + def pace(fdr, field, show_unit = true) + speed = fdr.get(field) + case @unit_system + when :metric + "#{speedToPace(speed)}#{show_unit ? ' min/km' : ''}" + when :statute + "#{speedToPace(speed, 1609.34)}#{show_unit ? ' min/mi' : ''}" + else + Log.fatal "Unknown unit system #{@unit_system}" + end + end + + end + +end + diff --git a/lib/postrunner/ActivityView.rb b/lib/postrunner/ActivityView.rb index 7cfe3fb..bde8d42 100644 --- a/lib/postrunner/ActivityView.rb +++ b/lib/postrunner/ActivityView.rb @@ -13,7 +13,7 @@ require 'fit4ruby' require 'postrunner/HTMLBuilder' -require 'postrunner/ActivityReport' +require 'postrunner/ActivitySummary' require 'postrunner/ViewWidgets' require 'postrunner/TrackView' require 'postrunner/ChartView' @@ -24,8 +24,9 @@ module PostRunner include ViewWidgets - def initialize(activity, predecessor, successor) + def initialize(activity, unit_system, predecessor, successor) @activity = activity + @unit_system = unit_system @predecessor = predecessor @successor = successor @output_dir = activity.html_dir @@ -39,9 +40,10 @@ module PostRunner private def generate_html(doc) - @report = ActivityReport.new(@activity) + @report = ActivitySummary.new(@activity.fit_activity, @activity.name, + @unit_system) @track_view = TrackView.new(@activity) - @chart_view = ChartView.new(@activity) + @chart_view = ChartView.new(@activity, @unit_system) doc.html { head(doc) diff --git a/lib/postrunner/ChartView.rb b/lib/postrunner/ChartView.rb index 3265aa1..5a68f0b 100644 --- a/lib/postrunner/ChartView.rb +++ b/lib/postrunner/ChartView.rb @@ -18,8 +18,9 @@ module PostRunner include ViewWidgets - def initialize(activity) + def initialize(activity, unit_system) @activity = activity + @unit_system = unit_system @empty_charts = {} end @@ -34,16 +35,29 @@ module PostRunner end def div(doc) - chart_div(doc, 'pace', 'Pace (min/km)') - chart_div(doc, 'altitude', 'Elevation (m)') + chart_div(doc, 'pace', "Pace (#{select_unit('min/km')})") + chart_div(doc, 'altitude', "Elevation (#{select_unit('m')})") chart_div(doc, 'heart_rate', 'Heart Rate (bpm)') - chart_div(doc, 'cadence', 'Run Cadence (spm)') - chart_div(doc, 'vertical_oscillation', 'Vertical Oscillation (cm)') + chart_div(doc, 'run_cadence', 'Run Cadence (spm)') + chart_div(doc, 'vertical_oscillation', + "Vertical Oscillation (#{select_unit('cm')})") chart_div(doc, 'stance_time', 'Ground Contact Time (ms)') end private + def select_unit(metric_unit) + case @unit_system + when :metric + metric_unit + when :statute + { 'min/km' => 'min/mi', 'm' => 'ft', 'cm' => 'in', + 'bpm' => 'bpm', 'spm' => 'spm', 'ms' => 'ms' }[metric_unit] + else + Log.fatal "Unknown unit system #{@unit_system}" + end + end + def style < 20.0 value = nil @@ -148,7 +162,7 @@ EOT s << "});\n" end - def point_graph(field, colors, multiplier = 1) + def point_graph(field, unit, colors) # We need to split the field values into separate data sets for each # color. The max value for each color determines which set a data point # ends up in. @@ -162,7 +176,6 @@ EOT @activity.fit_activity.records.each do |r| # Undefined values will be discarded. next unless (value = r.send(field)) - value *= multiplier # Find the right set by looking at the maximum allowed values for each # color. @@ -173,7 +186,7 @@ EOT # allowed range for this set, so add the value as x/y pair to the # set. x_val = (r.timestamp.to_i - start_time) * 1000 - data_sets[i] << [ x_val, value ] + data_sets[i] << [ x_val, r.get_as(field, select_unit(unit)) ] # Abort the color loop since we've found the right set already. break end diff --git a/lib/postrunner/Main.rb b/lib/postrunner/Main.rb index a2e6d11..25d9cd0 100644 --- a/lib/postrunner/Main.rb +++ b/lib/postrunner/Main.rb @@ -144,6 +144,9 @@ show [ ] summary Display the summary information for the FIT file. +units metric | statute + Change the unit system. + An absolute or relative name of a .FIT file. @@ -200,6 +203,8 @@ EOT end when 'summary' process_activities(args, :summary) + when 'units' + change_unit_system(args) when nil Log.fatal("No command provided. " + "See 'postrunner -h' for more information.") @@ -292,6 +297,17 @@ EOT end end + def change_unit_system(args) + if args.length != 1 || !%w( metric statute ).include?(args[0]) + Log.fatal("You must specify 'metric' or 'statute' as unit system.") + end + + if @cfg[:unit_system].to_s != args[0] + @cfg.set_option(:unit_system, args[0].to_sym) + @activities.generate_all_html_reports + end + end + end end diff --git a/lib/postrunner/RuntimeConfig.rb b/lib/postrunner/RuntimeConfig.rb index 0cc3e05..2bb9cb0 100644 --- a/lib/postrunner/RuntimeConfig.rb +++ b/lib/postrunner/RuntimeConfig.rb @@ -31,6 +31,13 @@ module PostRunner load_options if File.exist?(@config_file) end + # Shortcut for get_option. + # @param name [Symbol] the name of the config option. + # @return [Object] the value of the config option. + def [](name) + get_option(name) + end + # Get a config option value. # @param name [Symbol] the name of the config option. # @return [Object] the value of the config option. diff --git a/spec/ActivitySummary_spec.rb b/spec/ActivitySummary_spec.rb new file mode 100644 index 0000000..4e92017 --- /dev/null +++ b/spec/ActivitySummary_spec.rb @@ -0,0 +1,28 @@ +#!/usr/bin/env ruby -w +# encoding: UTF-8 +# +# = PostRunner_spec.rb -- PostRunner - Manage the data from your Garmin sport devices. +# +# Copyright (c) 2014 by Chris Schlaeger +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# + +require 'postrunner/ActivitySummary' +require 'spec_helper' + +describe PostRunner::ActivitySummary do + + before(:each) do + @as = PostRunner::ActivitySummary.new( + create_fit_activity('2014-08-26-19:00', 30), 'test', :metric) + end + + it 'should create a metric summary' do + @as.to_s #TODO: Fix aggregation first + end + +end + diff --git a/spec/FlexiTable_spec.rb b/spec/FlexiTable_spec.rb index f08e59a..5a03c56 100644 --- a/spec/FlexiTable_spec.rb +++ b/spec/FlexiTable_spec.rb @@ -19,7 +19,13 @@ describe PostRunner::FlexiTable do row(%w( a bb )) row(%w( ccc ddddd )) end - puts t.to_s + ref = < ts }) - a.total_timer_time = 30 * 60 - a.new_user_profile({ :timestamp => ts, - :age => 33, :height => 1.78, :weight => 73.0, - :gender => 'male', :activity_class => 4.0, - :max_hr => 178 }) - - a.new_event({ :timestamp => ts, :event => 'timer', - :event_type => 'start_time' }) - a.new_device_info({ :timestamp => ts, :device_index => 0 }) - a.new_device_info({ :timestamp => ts, :device_index => 1, - :battery_status => 'ok' }) - 0.upto(a.total_timer_time / 60) do |mins| - ts += 60 - a.new_record({ - :timestamp => ts, - :position_lat => 51.5512 - mins * 0.0008, - :position_long => 11.647 + mins * 0.002, - :distance => 200.0 * mins, - :altitude => 100 + mins * 0.5, - :speed => 3.1, - :vertical_oscillation => 9 + mins * 0.02, - :stance_time => 235.0 * mins * 0.01, - :stance_time_percent => 32.0, - :heart_rate => 140 + mins, - :cadence => 75, - :activity_type => 'running', - :fractional_cadence => (mins % 2) / 2.0 - }) - - if mins > 0 && mins % 5 == 0 - a.new_lap({ :timestamp => ts }) - end - end - a.new_session({ :timestamp => ts }) - a.new_event({ :timestamp => ts, :event => 'recovery_time', - :event_type => 'marker', - :data => 2160 }) - a.new_event({ :timestamp => ts, :event => 'vo2max', - :event_type => 'marker', :data => 52 }) - a.new_event({ :timestamp => ts, :event => 'timer', - :event_type => 'stop_all' }) - a.new_device_info({ :timestamp => ts, :device_index => 0 }) - ts += 1 - a.new_device_info({ :timestamp => ts, :device_index => 1, - :battery_status => 'low' }) - ts += 120 - a.new_event({ :timestamp => ts, :event => 'recovery_hr', - :event_type => 'marker', :data => 132 }) - - a.aggregate - Fit4Ruby.write(name, a) - end - before(:all) do @db_dir = File.join(File.dirname(__FILE__), '.postrunner') FileUtils.rm_rf(@db_dir) @@ -169,5 +114,13 @@ describe PostRunner::Main do postrunner(%w( dump FILE1.FIT )) end + it 'should switch to statute units' do + postrunner(%w( units statute )) + end + + it 'should switch back to metric units' do + postrunner(%w( units metric )) + end + end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..cd6fec4 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,62 @@ +def create_fit_file(name, date, duration_minutes = 30) + Fit4Ruby.write(name, create_fit_activity(date, duration_minutes)) +end + +def create_fit_activity(date, duration_minutes) + ts = Time.parse(date) + a = Fit4Ruby::Activity.new({ :timestamp => ts }) + a.total_timer_time = duration_minutes * 60 + a.new_user_profile({ :timestamp => ts, + :age => 33, :height => 1.78, :weight => 73.0, + :gender => 'male', :activity_class => 4.0, + :max_hr => 178 }) + + a.new_event({ :timestamp => ts, :event => 'timer', + :event_type => 'start_time' }) + a.new_device_info({ :timestamp => ts, :device_index => 0 }) + a.new_device_info({ :timestamp => ts, :device_index => 1, + :battery_status => 'ok' }) + 0.upto((a.total_timer_time / 60) - 1) do |mins| + a.new_record({ + :timestamp => ts, + :position_lat => 51.5512 - mins * 0.0008, + :position_long => 11.647 + mins * 0.002, + :distance => 200.0 * mins, + :altitude => 100 + mins * 0.5, + :speed => 3.1, + :vertical_oscillation => 9 + mins * 0.02, + :stance_time => 235.0 * mins * 0.01, + :stance_time_percent => 32.0, + :heart_rate => 140 + mins, + :cadence => 75, + :activity_type => 'running', + :fractional_cadence => (mins % 2) / 2.0 + }) + + ts += 60 + if (mins + 1) % 5 == 0 + a.new_lap({ :timestamp => ts }) + end + end + a.new_session({ :timestamp => ts }) + a.new_event({ :timestamp => ts, :event => 'recovery_time', + :event_type => 'marker', + :data => 2160 }) + a.new_event({ :timestamp => ts, :event => 'vo2max', + :event_type => 'marker', :data => 52 }) + a.new_event({ :timestamp => ts, :event => 'timer', + :event_type => 'stop_all' }) + a.new_device_info({ :timestamp => ts, :device_index => 0 }) + ts += 1 + a.new_device_info({ :timestamp => ts, :device_index => 1, + :battery_status => 'low' }) + ts += 120 + a.new_event({ :timestamp => ts, :event => 'recovery_hr', + :event_type => 'marker', :data => 132 }) + + a.aggregate + + a +end + + -- cgit v1.2.3