Implement handling exercise timer and statistics.
[profile/iot/apps/dotnet/fitness.git] / Fitness / Services / Exercises / BaseExerciseService.cs
1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Linq;
5 using System.Runtime.CompilerServices;
6 using System.Text;
7 using System.Threading;
8 using System.Threading.Tasks;
9 using Tizen.Multimedia;
10 using Tizen.Multimedia.Vision;
11 using Tizen.NUI;
12
13 namespace Fitness.Services
14 {
15     /// <summary>
16     /// Common base class for all exercise services.
17     /// </summary>
18     public abstract class BaseExerciseService
19     {
20         private readonly PoseDetector poseDetector = new PoseDetector();
21         private int isInferencing = 0;
22         private TimeSpan timeLeft;
23         private TimeSpan duration;
24         private Tizen.NUI.Timer timer;
25         private bool timerConfigured = false;
26         private bool workoutStopped = false;
27
28         /// <summary>
29         /// Event raised when workout time left has changed.
30         /// </summary>
31         public event EventHandler<TimeLeftChangedEventArgs> TimeLeftChanged;
32
33         /// <summary>
34         /// Event raised when workout time is up.
35         /// </summary>
36         public event EventHandler WorkoutTimeEnded;
37
38         /// <summary>
39         /// Gets or sets the duration of the workout.
40         /// </summary>
41         public TimeSpan Duration
42         {
43             get => duration;
44             set => duration = value;
45         }
46
47         /// <summary>
48         /// Gets the time remaining until the end of the exercise.
49         /// </summary>
50         public TimeSpan TimeLeft => timeLeft;
51
52         /// <summary>
53         /// Gets or sets a value indicating whether timer has been configured.
54         /// </summary>
55         protected bool TimerConfigured
56         {
57             get => timerConfigured;
58             set => timerConfigured = value;
59         }
60
61         /// <summary>
62         /// Detects performing the exercise based on given preview image data.
63         /// </summary>
64         /// <param name="previewFrame">Preview image data.</param>
65         /// <returns>A task that represents the exercise detection.</returns>
66         public async Task DetectExercise(Tizen.Multimedia.PreviewFrame previewFrame)
67         {
68             var stopwatch = new System.Diagnostics.Stopwatch();
69             stopwatch.Start();
70
71             try
72             {
73                 if (Interlocked.Exchange(ref isInferencing, 1) == 0)
74                 {
75                     var plane = previewFrame.Plane as TriplePlane;
76                     if (plane is null)
77                     {
78                         throw new System.Exception($"Unsupported plane type: {previewFrame.Plane.GetType()}");
79                     }
80
81                     uint width = (uint)previewFrame.Resolution.Width;
82                     uint height = (uint)previewFrame.Resolution.Height;
83
84                     var landmarks = await poseDetector.Detect(plane, height, width);
85
86                     DetectExercise(landmarks);
87                     if (workoutStopped)
88                     {
89                         return;
90                     }
91                 }
92             }
93             catch (System.Exception exception)
94             {
95                 Services.Logger.Error(exception.Message + System.Environment.NewLine + exception.StackTrace);
96             }
97             finally
98             {
99                 stopwatch.Stop();
100                 Services.Logger.Debug($"total time {stopwatch.ElapsedMilliseconds} msec");
101
102                 Interlocked.Exchange(ref isInferencing, 0);
103             }
104         }
105
106         /// <summary>
107         /// Starts workout.
108         /// </summary>
109         public void StartWorkout()
110         {
111             if (!TimerConfigured)
112             {
113                 ConfigureTimer();
114             }
115
116             timer?.Start();
117             workoutStopped = false;
118         }
119
120         /// <summary>
121         /// Stops workout.
122         /// </summary>
123         public void StopWorkout()
124         {
125             timer?.Stop();
126             workoutStopped = true;
127         }
128
129         /// <summary>
130         /// Detects performing the exercise based on given landmarks.
131         /// </summary>
132         /// <param name="landmarks">Body landmarks.</param>
133         protected abstract void DetectExercise(Landmark[,] landmarks);
134
135         private void ConfigureTimer()
136         {
137             timer = new Tizen.NUI.Timer(1000);
138             timeLeft = Duration;
139
140             timer.Tick += (sender, args) =>
141             {
142                 if (timeLeft.CompareTo(TimeSpan.FromSeconds(0)) <= 0)
143                 {
144                     StopWorkout();
145
146                     WorkoutTimeEnded?.Invoke(this, EventArgs.Empty);
147                     return true;
148                 }
149
150                 timeLeft = timeLeft.Subtract(TimeSpan.FromSeconds(1));
151                 TimeLeftChanged?.Invoke(this, new TimeLeftChangedEventArgs()
152                 {
153                     TimeLeft = timeLeft,
154                 });
155                 return true;
156             };
157             TimerConfigured = true;
158         }
159     }
160 }