diff options
-rw-r--r-- | lib/postrunner/DailySleepAnalyzer.rb | 46 | ||||
-rw-r--r-- | lib/postrunner/FitFileStore.rb | 4 | ||||
-rw-r--r-- | lib/postrunner/SleepStatistics.rb | 69 |
3 files changed, 87 insertions, 32 deletions
diff --git a/lib/postrunner/DailySleepAnalyzer.rb b/lib/postrunner/DailySleepAnalyzer.rb index ef8d279..a120d95 100644 --- a/lib/postrunner/DailySleepAnalyzer.rb +++ b/lib/postrunner/DailySleepAnalyzer.rb @@ -20,10 +20,11 @@ module PostRunner class DailySleepAnalyzer # Utility class to store the interval of a sleep/wake phase. - class SleepPeriod < Struct.new(:from_time, :to_time, :phase) + class SleepInterval < Struct.new(:from_time, :to_time, :phase) end - include Fit4Ruby::Converters + attr_reader :sleep_intervals, :utc_offset, + :total_sleep, :deep_sleep, :light_sleep # Create a new DailySleepAnalyzer object to analyze the given monitoring # files. @@ -31,7 +32,7 @@ module PostRunner # @param day [String] Day to analyze as YY-MM-DD string def initialize(monitoring_files, day) @noon_yesterday = @noon_today = @utc_offset = nil - @sleep_periods = [] + @sleep_intervals = [] # Day as Time object. Midnight UTC. day_as_time = Time.parse(day + "-00:00:00+00:00").gmtime @@ -43,22 +44,6 @@ module PostRunner calculate_totals end - def to_s - unless @noon_today - return 'No sleep data available for this day' - end - - s = "Sleep periods for #{@noon_today.strftime('%Y-%m-%d')}\n" - @sleep_periods.each do |p| - s += "#{p.from_time.localtime(@utc_offset).strftime('%H:%M')} - " + - "#{p.to_time.localtime(@utc_offset).strftime('%H:%M')} : " + - "#{p.phase.to_s.gsub(/_/, ' ')}\n" - end - s += "\nTotal sleep time: #{secsToHM(@total_sleep)} (" + - "Deep: #{secsToHM(@deep_sleep)}, " + - "Light: #{secsToHM(@light_sleep)})" - end - private def extract_utc_offset(monitoring_file) @@ -163,7 +148,7 @@ module PostRunner def analyze current_phase = :awake current_phase_start = @noon_yesterday - @sleep_periods = [] + @sleep_intervals = [] @smoothed_sleep_activity.each_with_index do |v, idx| if v < 0.25 @@ -176,20 +161,20 @@ module PostRunner if current_phase != phase t = @noon_yesterday + 60 * idx - @sleep_periods << SleepPeriod.new(current_phase_start, t, - current_phase) + @sleep_intervals << SleepInterval.new(current_phase_start, t, + current_phase) current_phase = phase current_phase_start = t end end - @sleep_periods << SleepPeriod.new(current_phase_start, @noon_today, - current_phase) + @sleep_intervals << SleepInterval.new(current_phase_start, @noon_today, + current_phase) end def trim_wake_periods_at_ends first_deep_sleep_idx = last_deep_sleep_idx = nil - @sleep_periods.each_with_index do |p, idx| + @sleep_intervals.each_with_index do |p, idx| if p.phase == :deep_sleep || (p.phase == :light_sleep && ((p.to_time - p.from_time) > 15 * 60)) first_deep_sleep_idx = idx unless first_deep_sleep_idx @@ -200,20 +185,21 @@ module PostRunner return unless first_deep_sleep_idx && last_deep_sleep_idx if first_deep_sleep_idx > 0 && - @sleep_periods[first_deep_sleep_idx - 1].phase == :light_sleep + @sleep_intervals[first_deep_sleep_idx - 1].phase == :light_sleep first_deep_sleep_idx -= 1 end - if last_deep_sleep_idx < @sleep_periods.length - 2 && - @sleep_periods[last_deep_sleep_idx + 1].phase == :light_sleep + if last_deep_sleep_idx < @sleep_intervals.length - 2 && + @sleep_intervals[last_deep_sleep_idx + 1].phase == :light_sleep last_deep_sleep_idx += 1 end - @sleep_periods = @sleep_periods[first_deep_sleep_idx..last_deep_sleep_idx] + @sleep_intervals = + @sleep_intervals[first_deep_sleep_idx..last_deep_sleep_idx] end def calculate_totals @total_sleep = @light_sleep = @deep_sleep = 0 - @sleep_periods.each do |p| + @sleep_intervals.each do |p| if p.phase != :awake seconds = p.to_time - p.from_time @total_sleep += seconds diff --git a/lib/postrunner/FitFileStore.rb b/lib/postrunner/FitFileStore.rb index a088e7b..75bd104 100644 --- a/lib/postrunner/FitFileStore.rb +++ b/lib/postrunner/FitFileStore.rb @@ -18,7 +18,7 @@ require 'postrunner/DirUtils' require 'postrunner/FFS_Device' require 'postrunner/ActivityListView' require 'postrunner/ViewButtons' -require 'postrunner/DailySleepAnalyzer' +require 'postrunner/SleepStatistics' module PostRunner @@ -338,7 +338,7 @@ module PostRunner read_fit_file(File.join(fit_file_dir(m.fit_file_name, m.device.long_uid, 'monitor'), m.fit_file_name)) end - puts DailySleepAnalyzer.new(monitoring_files, day).to_s + puts SleepStatistics.new(monitoring_files).daily(day) end private diff --git a/lib/postrunner/SleepStatistics.rb b/lib/postrunner/SleepStatistics.rb new file mode 100644 index 0000000..514733b --- /dev/null +++ b/lib/postrunner/SleepStatistics.rb @@ -0,0 +1,69 @@ +#!/usr/bin/env ruby -w +# encoding: UTF-8 +# +# = SleepStatistics.rb -- PostRunner - Manage the data from your Garmin sport devices. +# +# Copyright (c) 2016 by Chris Schlaeger <cs@taskjuggler.org> +# +# 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/DailySleepAnalyzer' +require 'postrunner/FlexiTable' + +module PostRunner + + # This class can be used to generate reports for sleep data. It uses the + # DailySleepAnalyzer class to compute the data and generates the report for + # a certain time period. + class SleepStatistics + + include Fit4Ruby::Converters + + # Create a new SleepStatistics object. + # @param monitoring_files [Array of Fit4Ruby::Monitoring_B] FIT files + def initialize(monitoring_files) + @monitoring_files = monitoring_files + end + + # Generate a report for a certain day. + # @param day [String] Date of the day as YYYY-MM-DD string. + def daily(day) + analyzer = DailySleepAnalyzer.new(@monitoring_files, day) + + if analyzer.sleep_intervals.empty? + return 'No sleep data available for this day' + end + + ti = FlexiTable.new + ti.head + ti.row([ 'From', 'To', 'Sleep phase' ]) + ti.body + utc_offset = analyzer.utc_offset + analyzer.sleep_intervals.each do |i| + ti.cell(i[:from_time].localtime(utc_offset).strftime('%H:%M')) + ti.cell(i[:to_time].localtime(utc_offset).strftime('%H:%M')) + ti.cell(i[:phase]) + ti.new_row + end + + tt = FlexiTable.new + tt.head + tt.row([ 'Total Sleep', 'Deep Sleep', 'Light Sleep' ]) + tt.body + tt.cell(secsToHM(analyzer.total_sleep), { :halign => :right }) + tt.cell(secsToHM(analyzer.deep_sleep), { :halign => :right }) + tt.cell(secsToHM(analyzer.light_sleep), { :halign => :right }) + tt.new_row + + "Sleep Statistics for #{day}\n\n#{ti}\n#{tt}" + end + + end + +end + |