diff --git a/code/src/Fitbot/.idea/misc.xml b/code/src/Fitbot/.idea/misc.xml
index 6d5e29f..a647bf6 100644
--- a/code/src/Fitbot/.idea/misc.xml
+++ b/code/src/Fitbot/.idea/misc.xml
@@ -20,6 +20,7 @@
+
@@ -32,7 +33,7 @@
-
+
diff --git a/code/src/Fitbot/app/build.gradle b/code/src/Fitbot/app/build.gradle
index e63614b..1348df8 100644
--- a/code/src/Fitbot/app/build.gradle
+++ b/code/src/Fitbot/app/build.gradle
@@ -38,9 +38,6 @@ dependencies {
implementation 'org.joml:joml:1.10.5'
implementation 'com.google.code.gson:gson:2.8.6'
testImplementation 'junit:junit:4.13.2'
- testImplementation 'org.junit.jupiter:junit-jupiter'
- testImplementation 'org.junit.jupiter:junit-jupiter'
- testImplementation 'org.junit.jupiter:junit-jupiter'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.aldebaran:qisdk:1.7.5'
diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/EMuscleGroup.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/EMuscleGroup.java
index 27ff244..e85d0da 100644
--- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/EMuscleGroup.java
+++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/EMuscleGroup.java
@@ -3,21 +3,31 @@ package com.example.fitbot.exercise;
public enum EMuscleGroup {
// TODO: Implement
- TORSO(0),
- ARMS(1),
- LEGS(2),
- BALANCE(3);
+ TORSO(0, new String[]{"upper body", "torso"}),
+ ARMS(1, new String[]{"arms", "arm", "shoulder"}),
+ LEGS(2, new String[]{"Lower body", "legs", "leg"});
int muscleGroupIdentifier;
+ String[] muscleGroupNames;
- EMuscleGroup(int identifier) {
+ EMuscleGroup(int identifier, String[] muscleGroupNames) {
this.muscleGroupIdentifier = identifier;
+ this.muscleGroupNames = muscleGroupNames;
}
public int getIdentifier() {
return this.muscleGroupIdentifier;
}
+ public static EMuscleGroup parse(String name)
+ {
+ for ( EMuscleGroup muscleGroup : EMuscleGroup.values())
+ for ( String muscleGroupName : muscleGroup.muscleGroupNames)
+ if ( muscleGroupName.equalsIgnoreCase(name))
+ return muscleGroup;
+ return null;
+ }
+
public static EMuscleGroup parse(int identifier) {
for (EMuscleGroup muscleGroup : EMuscleGroup.values()) {
if (muscleGroup.getIdentifier() == identifier) {
diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/Exercise.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/Exercise.java
index 20758b5..d8198b6 100644
--- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/Exercise.java
+++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/Exercise.java
@@ -3,26 +3,21 @@ package com.example.fitbot.exercise;
import android.util.Log;
import com.example.fitbot.util.path.GesturePath;
-import com.example.fitbot.util.processing.IMotionDataConsumer;
import com.example.fitbot.util.server.IWebServerHandler;
import com.example.fitbot.util.server.WebServer;
import java.util.Objects;
-import java.util.function.Consumer;
-public class Exercise implements IWebServerHandler {
-
- private EMuscleGroup muscleGroup;
- private GesturePath leftPath;
- private GesturePath rightPath;
- private String title;
- private String description;
- private float segmentsPerSecond;
-
- // Static fields.
- private static WebServer webSocket;
- private static Exercise currentExercise = null;
+public class Exercise {
+ public final EMuscleGroup muscleGroup;
+ public final GesturePath leftPath;
+ public final GesturePath rightPath;
+ public final String title;
+ public final String description;
+ public final String imageUrl;
+ public final String videoUrl;
+ public final float exerciseTimeInSeconds;
/**
* Constructor for the AbstractExercise class.
@@ -32,106 +27,17 @@ public class Exercise implements IWebServerHandler {
* @param rightPath The path of the right hand.
* @param title The title of the exercise.
* @param description The description of the exercise.
- * @param segmentsPerSecond The number of segments per second.
- * This determines how fast the exercise should be performed.
+ * @param imageUrl The URL of the image.
+ * @param videoUrl The URL of the video.
*/
- public Exercise(EMuscleGroup muscleGroup, String title, String description, GesturePath leftPath, GesturePath rightPath) {
+ public Exercise(EMuscleGroup muscleGroup, String title, String description, String imageUrl, String videoUrl, GesturePath leftPath, GesturePath rightPath, float exerciseTimeInSeconds) {
this.muscleGroup = muscleGroup;
this.title = title;
this.description = description;
this.leftPath = leftPath;
this.rightPath = rightPath;
- }
-
- /**
- * Start the exercise.
- * This method starts a WebSocket server
- */
- public final void startExercise() {
-
- // Ensure no other exercise is active.
- if (currentExercise != null && currentExercise != this) {
- currentExercise.__stopExercise();
- Log.i("Exercises", "Another exercise was started when another was still running.");
- }
-
- // If a WebSocket server is already running, change the event handler to be this class.
- if (webSocket != null && webSocket.isConnected()) {
- webSocket.setEventHandler(this);
- }
-
- try {
- webSocket = WebServer.createServer();
- Objects.requireNonNull(webSocket, "WebSocket server could not be created.");
-
- webSocket.setEventHandler(this);
- currentExercise = this;
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- /**
- * Method for ending this exercise and returning the grade of the performance
- * of this activity.
- */
- public final double finishExercise() {
- this.__stopExercise();
-
- // TODO: Implement grade calculation
- return 0.0;
- }
-
- /**
- * Stop the exercise.
- * This method stops the WebSocket server.
- */
- private void __stopExercise() {
- if (webSocket != null && webSocket.isConnected()) {
- webSocket.stop();
- webSocket = null;
- }
- currentExercise = null;
- }
-
- /**
- * Check if the current exercise is the current activity.
- */
- public final boolean isCurrentActivity() {
- return currentExercise == this;
- }
-
- /**
- * Get the muscle group of the exercise.
- */
- public EMuscleGroup getMuscleGroup() {
- return muscleGroup;
- }
-
- /**
- * Get the path of the exercise.
- */
- public GesturePath[] getPath() {
- return new GesturePath[]{leftPath, rightPath};
- }
-
- public String getTitle() {
- return title;
- }
-
- public String getDescription() {
- return description;
- }
-
- /**
- * Get the speed of the exercise.
- */
- public double getSegmentsPerSecond() {
- return segmentsPerSecond;
- }
-
- @Override
- public void onReceive(String message) {
-
+ this.imageUrl = imageUrl;
+ this.videoUrl = videoUrl;
+ this.exerciseTimeInSeconds = exerciseTimeInSeconds;
}
}
diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/ExerciseManager.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/ExerciseManager.java
index a500943..e4ad128 100644
--- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/ExerciseManager.java
+++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/ExerciseManager.java
@@ -4,25 +4,35 @@ import com.example.fitbot.util.path.GesturePath;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
-import org.joml.Vector3f;
-
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
-import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLConnection;
public class ExerciseManager {
- private static final String HOST_ADDRESS = "http://145.92.8.132";
+ private static final String HOST_ADDRESS = "http://145.92.8.132:443/";
+ // The value of these property variables must be equivalent of
+ // the JSON data that the database sends back.
+ // If this is not the case then the exercise retrieval will fail.
+ private static final String PROPERTY_MUSCLE_GROUP = "muscleGroup";
private static final String PROPERTY_DESC = "description";
- private static final String PROPERTY_VECTORS = "vector_data";
+ private static final String PROPERTY_IMAGE_URL = "imageUrl";
+ private static final String PROPERTY_VIDEO_URL = "videoUrl";
private static final String PROPERTY_NAME = "name";
- private static final String PROPERTY_MUSCLE_GROUP = "muscle_group";
- private static final String PROPERTY_SEGMENT_SPEED = "segment_speed";
+ private static final String PROPERTY_DATA = "data";
+ private static final String PROPERTY_EXERCISE_DURATION = "exerciseDuration";
+
+ public static final int SENSOR_COUNT = 2;
+
+ private static final String[] REQUIRED_PROPERTIES = {
+ PROPERTY_MUSCLE_GROUP, PROPERTY_DESC, PROPERTY_IMAGE_URL,
+ PROPERTY_VIDEO_URL, PROPERTY_NAME, PROPERTY_DATA,
+ PROPERTY_EXERCISE_DURATION
+ };
private static final float DEFAULT_SEGMENT_SPEED = 1.0f;
@@ -36,13 +46,17 @@ public class ExerciseManager {
*
* @return The response from the server.
*/
- private static String sendHTTP(String url, String method, String contentType, String body) {
+ public static String sendHTTP(String url, String method, String contentType, String body) {
try {
URLConnection connection = new URL(url).openConnection();
+ connection.setDoOutput(true);
+ connection.setDoInput(true);
connection.addRequestProperty("Content-Type", contentType);
connection.addRequestProperty("Request-Method", method);
- connection.getOutputStream().write(body.getBytes());
connection.connect();
+ // Send a body if it is present
+ if (body != null)
+ connection.getOutputStream().write(body.getBytes());
InputStream stream = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
StringBuilder builder = new StringBuilder();
@@ -60,23 +74,41 @@ public class ExerciseManager {
/**
* Function for retrieving an exercise from the Raspberry Pi Database.
*
- * @param uniqueIdentifier The unique identifier of the exercise
* @return The exercise, if it exists on the server. Otherwise null.
*/
- public static Exercise retrieveExercise(String uniqueIdentifier) {
+ public static Exercise retrieveExercise() {
String response = sendHTTP(
- HOST_ADDRESS + "/acquire", "GET", "application/json", "{\"kind\":\"" + uniqueIdentifier + "\"}"
+ HOST_ADDRESS, "POST", "application/json", null
);
// Validate the response
if (response != null) {
try {
JsonObject content = JsonParser.parseString(response).getAsJsonObject();
+
+ // Ensure all required properties are present
+ for (String property : REQUIRED_PROPERTIES) {
+ if (!content.has(property)) {
+ return null;
+ }
+ }
+
+ // Path data is split into two parts, due to the left and right hand.
+ // If one wants to add support for more sensors, one will have to adjust the Exercise
+ // class to support more paths.
+ String[] leftRightData = content.get(PROPERTY_DATA).getAsString().split(";");
+
+ if ( leftRightData.length != SENSOR_COUNT)
+ return null;
+
return new Exercise(
- EMuscleGroup.parse(content.get(PROPERTY_MUSCLE_GROUP).getAsInt()),
+ EMuscleGroup.parse(content.get(PROPERTY_MUSCLE_GROUP).getAsString()),
content.get(PROPERTY_NAME).getAsString(),
content.get(PROPERTY_DESC).getAsString(),
- gesturePathFromString(content.get(PROPERTY_VECTORS).getAsString()),
- gesturePathFromString(content.get(PROPERTY_SEGMENT_SPEED).getAsString())
+ content.get(PROPERTY_IMAGE_URL).getAsString(),
+ content.get(PROPERTY_VIDEO_URL).getAsString(),
+ GesturePath.fromString(leftRightData[0]),
+ GesturePath.fromString(leftRightData[1]),
+ DEFAULT_SEGMENT_SPEED
);
} catch (Exception e) {
e.printStackTrace();
@@ -84,42 +116,4 @@ public class ExerciseManager {
}
return null;
}
-
- /**
- * Function for converting a string to a GesturePath object.
- * The input string bytes will be directly converted into 3d vectors.
- * Every scalar is composed of 32 bits (4 characters), meaning 96 bits per vector.
- *
- * Note: ASCII to Vector conversion is done in Big Endian format (most significant byte first).
- *
- * @param input The string to convert
- * @return The GesturePath object
- */
- private static GesturePath gesturePathFromString(String input) {
- byte[] bytes = input.getBytes();
-
- // Check if the input string contains a valid amount of bytes (12 bytes per vector)
- if (input.length() % 12 != 0) {
- throw new IllegalArgumentException("Invalid input string length");
- }
- GesturePath.Builder builder = new GesturePath.Builder();
-
- float[] xyz = new float[3];
- for (int i = 0; i < bytes.length; i += 12) {
- for (int j = 0; j < 3; j++) {
-
- xyz[j] = Float.intBitsToFloat(
- (bytes[i + j * 4] & 0xFF) << 24 |
- (bytes[i + j * 4 + 1] & 0xFF) << 16 |
- (bytes[i + j * 4 + 2] & 0xFF) << 8 |
- (bytes[i + j * 4 + 3] & 0xFF)
- );
- }
- builder.addVector(new Vector3f(
- xyz[0], xyz[1], xyz[2]
- ));
- }
- return builder.build();
- }
-
}
diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/FitnessActivity.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/FitnessActivity.java
index 2c53230..5d2d6dd 100644
--- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/FitnessActivity.java
+++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/FitnessActivity.java
@@ -2,6 +2,7 @@ package com.example.fitbot.ui.activities;
import android.os.Bundle;
import android.util.Log;
+import android.view.View;
import android.widget.VideoView;
import com.aldebaran.qi.sdk.QiContext;
@@ -10,25 +11,43 @@ import com.aldebaran.qi.sdk.RobotLifecycleCallbacks;
import com.aldebaran.qi.sdk.design.activity.RobotActivity;
import com.aldebaran.qi.sdk.design.activity.conversationstatus.SpeechBarDisplayStrategy;
import com.example.fitbot.R;
-import com.example.fitbot.exercise.EMuscleGroup;
import com.example.fitbot.exercise.Exercise;
+import com.example.fitbot.exercise.ExerciseManager;
import com.example.fitbot.ui.components.PersonalMotionPreviewElement;
import com.example.fitbot.util.ButtonNavigation;
import com.example.fitbot.util.FitnessCycle;
-import com.example.fitbot.util.path.GesturePath;
+import com.example.fitbot.util.processing.InputProcessor;
import org.joml.Vector3f;
-import java.util.concurrent.CompletableFuture;
-
public class FitnessActivity extends RobotActivity implements RobotLifecycleCallbacks {
- PersonalMotionPreviewElement personalMotionPreviewElement;
+ // Private fields for the FitnessActivity class.
+ private PersonalMotionPreviewElement personalMotionPreviewElement;
+ private InputProcessor motionProcessor;
+ private Exercise currentExercise;
+
+ private QiContext qiContext;
+
+ // Some nice little messages for the user
+ private static final String[] EXERCISE_NOT_FOUND_MESSAGES = new String[] {
+ "Ik heb momenteel helaas wat moeite met het ophalen van oefeningen, sorry.",
+ "Het lijkt erop dat de oefeningen op een misterieus avontuur zijn. Even wachten tot ze terug zijn.",
+ "Ssst, de oefeningen slapen nog, probeer het later nog eens."
+ };
+
+ private static final String EXERCISE_NOT_FOUND_SEEK_HELP_MESSAGE =
+ "Indien dit probleem zich voortzet, neem contact op met de ontwikkelaar.";
+
+ private static final float SENSOR_SAMPLE_RATE = 10.0f;
+ private static final int EXERCISE_COUNT = 5;
+ private static final float EXERCISE_SPEED_MULTIPLIER = 1.0f;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
QiSDK.register(this, this);
+
setContentView(R.layout.activity_fitness);
// Remove the ugly ass bar on top of the view
@@ -36,49 +55,62 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
// Find the VideoView by its ID
VideoView videoView = findViewById(R.id.videoView);
-
FitnessCycle.playVideo(videoView, this);
-
ButtonNavigation.setupButtonNavigation(this, R.id.homeButtonFitness, MainActivity.class);
// Implement your logic when the robot focus is gained
- GesturePath.Builder gesturePathBuilder = new GesturePath.Builder();
-
- /* Generate a random path to test the tracking system */
- for ( int i = 0; i < 40; i++)
- {
- gesturePathBuilder.addVector(
- new Vector3f(
- (float)Math.cos(Math.PI + (Math.PI / 40.0f) * i),
- (float)Math.sin(Math.PI + (Math.PI / 40.0f) * i),
- 0
- )
- );
- }
-
- personalMotionPreviewElement = findViewById(R.id.personalMotionPreviewElement);
- personalMotionPreviewElement.post(() -> {
- Log.i("FitnessActivity", "PersonalMotionPreviewElement.post()");
-
- Exercise exercise = new Exercise(EMuscleGroup.ARMS, "Bicep Curls", "Oefening voor de biceps.", gesturePathBuilder.build(), gesturePathBuilder.build());
-
- personalMotionPreviewElement.initialize(exercise);
- personalMotionPreviewElement.provideQiContext(null);
- });
-
}
@Override
public void onRobotFocusGained(QiContext qiContext) {
-
+ this.qiContext = qiContext;
// Find the VideoView by its ID
// CompletableFuture.runAsync(() -> FitnessCycle.executeMovement("bicepcurl", 10, qiContext));
- Log.i("Motion", "qiContext provided");
+
+ personalMotionPreviewElement = findViewById(R.id.personalMotionPreviewElement);
+
+ // Initialize the element whenever it has been added to the screen.
+ // This will provide the element with the appropriate dimensions for drawing
+ // the canvas properly.
+ personalMotionPreviewElement.post(() -> {
+ Exercise exercise = this.acquireExercise();
+ if ( exercise == null) {
+ return;
+ }
+ // Acquire paths from the exercise and provide them to the motion processor
+ Vector3f[][] vectors = new Vector3f[][] {exercise.leftPath.getVectors(), exercise.rightPath.getVectors()};
+
+ motionProcessor = new InputProcessor(vectors, exercise.exerciseTimeInSeconds, SENSOR_SAMPLE_RATE);
+
+ personalMotionPreviewElement.provideQiContext(qiContext);
+ personalMotionPreviewElement.initialize(exercise, motionProcessor, EXERCISE_COUNT);
+
+ motionProcessor.startListening();
+ motionProcessor.setInputHandler(personalMotionPreviewElement);
+ });
personalMotionPreviewElement.provideQiContext(qiContext);
// FitnessCycle.playVideo(qiContext, videoView, this);
}
+ /**
+ * Acquire an exercise from the ExerciseManager.
+ * Whenever the retrieval failed, it will have the robot say something to the user
+ * to inform them about the issue.
+ *
+ * @return The acquired exercise, or null if the exercise could not be retrieved.
+ */
+ public Exercise acquireExercise() {
+ Exercise exercise = ExerciseManager.retrieveExercise();
+ if ( exercise == null && this.qiContext != null)
+ {
+ int randomMessageIndex = (int)Math.floor(Math.random() * EXERCISE_NOT_FOUND_MESSAGES.length);
+ FitnessCycle.say(EXERCISE_NOT_FOUND_MESSAGES[randomMessageIndex], qiContext);
+ FitnessCycle.say(EXERCISE_NOT_FOUND_SEEK_HELP_MESSAGE, qiContext);
+ }
+ return exercise;
+ }
+
@Override
public void onRobotFocusLost() {
// Implement your logic when the robot focus is lost
@@ -93,6 +125,8 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
protected void onDestroy() {
super.onDestroy();
QiSDK.unregister(this, this);
- this.personalMotionPreviewElement.onDestroy();
+ this.motionProcessor.stopListening();
+ this.motionProcessor = null;
+ this.personalMotionPreviewElement.destroy();
}
}
\ No newline at end of file
diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/HelpActivity.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/HelpActivity.java
index 765ff86..cbc714c 100644
--- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/HelpActivity.java
+++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/HelpActivity.java
@@ -2,6 +2,7 @@ package com.example.fitbot.ui.activities;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
+import android.view.View;
import com.example.fitbot.R;
import com.example.fitbot.util.ButtonNavigation;
@@ -15,5 +16,16 @@ public class HelpActivity extends AppCompatActivity {
ButtonNavigation.setupButtonNavigation(this, R.id.homeButtonHelp, MainActivity.class);
+ // Hide system UI
+ hideSystemUI();
+ }
+
+ private void hideSystemUI() {
+ View decorView = getWindow().getDecorView();
+ // Hide the status bar and navigation bar
+ int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+ decorView.setSystemUiVisibility(uiOptions);
}
}
\ No newline at end of file
diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/MainActivity.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/MainActivity.java
index be78e12..4edfa2e 100644
--- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/MainActivity.java
+++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/MainActivity.java
@@ -32,12 +32,10 @@ public class MainActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
- // Set full screen mode
+ // Set full screen mode to hide status bar
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
- // Hide system UI
- hideSystemUI();
startButton = findViewById(R.id.startButtonMain);
startButton.setOnClickListener(v -> {
@@ -47,7 +45,11 @@ public class MainActivity extends AppCompatActivity {
startActivity(intent);
});
- setUpUi(); // Set up the UI
+ // Set up the UI
+ setUpUi();
+
+ // Hide system UI
+ hideSystemUI();
}
private void setUpUi() {
diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java
index ef41c2e..92f4831 100644
--- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java
+++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java
@@ -1,18 +1,23 @@
package com.example.fitbot.ui.components;
+import android.app.Activity;
import android.content.Context;
+import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Paint;
-import android.graphics.Path;
+import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import com.aldebaran.qi.sdk.QiContext;
import com.example.fitbot.exercise.Exercise;
+import com.example.fitbot.ui.activities.EndScreenActivity;
+import com.example.fitbot.ui.activities.FitnessActivity;
+import com.example.fitbot.ui.activities.MainActivity;
import com.example.fitbot.util.FitnessCycle;
-import com.example.fitbot.util.path.GesturePath;
-import com.example.fitbot.util.processing.MotionProcessor;
+import com.example.fitbot.util.processing.IInputHandler;
+import com.example.fitbot.util.processing.InputProcessor;
import org.joml.Matrix4f;
import org.joml.Vector2f;
@@ -20,61 +25,58 @@ import org.joml.Vector3f;
import org.joml.Vector4f;
import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.atomic.AtomicInteger;
-public class PersonalMotionPreviewElement extends View {
-
- private GesturePath[] paths;
- private MotionProcessor motionProcessor;
-
- private double pathTime = 0.0D; // The timestamp at which the path is currently at.
- private final AtomicInteger exerciseProgress = new AtomicInteger(0); // The progress of the exercise. Ranges from 0 to 1000.
-
- private QiContext qiContext;
+public class PersonalMotionPreviewElement extends View implements IInputHandler {
+ // Fields regarding Exercise and speech handling.
+ private InputProcessor motionProcessor;
private Exercise exercise;
+ private QiContext qiContext;
+ private int exerciseCount;
- private Path targetPath; // The path the user is supposed to follow.
- private Path actualPath; // The path the user is currently following.
+ private FitnessActivity parentActivity;
- private final Paint referencePaint = new Paint();
- private final Paint targetPaint = new Paint();
- private final Paint backgroundColor = new Paint();
+ private final Paint userProgressPaint = new Paint();
+ private final Paint borderPaint = new Paint();
+ private final Paint backgroundPaint = new Paint();
+ // TODO: Remove
private Matrix4f viewMatrix = new Matrix4f(); // The view matrix for the 3D to 2D transformation.
private Matrix4f projectionMatrix = new Matrix4f(); // The projection matrix for the 3D to 2D transformation.
private final Vector4f objectPosition = new Vector4f(0, 0, 0, 1); // The location of the object in 3D space.
private ConcurrentLinkedQueue vectors = new ConcurrentLinkedQueue<>();
+ // TODO: Remove
private Vector2f[] axisVectors = new Vector2f[0];
-
- private static final String[] USER_PHRASES = {
+ private static final String[] STARTING_PHRASES = {
"Veel success met de oefening!",
"Je kan het!",
"Veel plezier!"
};
- private double timePassed = 0.0D; // The time that has passed since the start of the exercise, in seconds.
- private long startingTime = 0L;
-
- private Vector2f screenDimensions = new Vector2f(); // Width and height dimensions of the screen
public PersonalMotionPreviewElement(Context context, AttributeSet attrs) {
super(context, attrs);
- this.referencePaint.setColor(0xFFFF0000); // Red
- this.referencePaint.setStyle(Paint.Style.FILL);
- this.referencePaint.setStrokeWidth(5.0f);
- this.referencePaint.setAntiAlias(true);
+ if ( context instanceof Activity)
+ {
+ this.parentActivity = (FitnessActivity) context;
+ }
+
+ this.userProgressPaint.setColor(0xFFFF0000); // Red
+ this.userProgressPaint.setStyle(Paint.Style.FILL);
+ this.userProgressPaint.setStrokeWidth(5.0f);
+ this.userProgressPaint.setAntiAlias(true);
// Target paint is the filling of the target path.
- this.targetPaint.setColor(-1);
- this.targetPaint.setStyle(Paint.Style.STROKE);
- this.targetPaint.setStrokeWidth(5.0f);
- this.targetPaint.setAntiAlias(true);
+ this.borderPaint.setColor(-1);
+ this.borderPaint.setStyle(Paint.Style.STROKE);
+ this.borderPaint.setStrokeWidth(5.0f);
+ this.borderPaint.setAntiAlias(true);
+
+ this.backgroundPaint.setColor(0xFF000000); // Black
}
/**
@@ -84,34 +86,28 @@ public class PersonalMotionPreviewElement extends View {
* will cause the vertex projections to fail (0 width and height).
*
* @param exercise The exercise that the user is currently performing.
+ * @param motionProcessor The motion processor that will be used to process the user's motion.
+ * @param exerciseCount The total amount of exercises that the user has to perform.
*/
- public void initialize(Exercise exercise) {
+ public void initialize(@Nullable Exercise exercise, InputProcessor motionProcessor, int exerciseCount) {
Log.i("PersonalMotionPreviewElement", "Creating new PersonalMotionPreviewElement.");
- this.backgroundColor.setColor(0xFF000000); // Black
-
- this.screenDimensions.x = this.getWidth();
- this.screenDimensions.y = this.getHeight();
- this.actualPath = new Path();
- this.targetPath = new Path();
-
- this.startingTime = System.nanoTime(); // Set the last time to the current time
+ this.motionProcessor = motionProcessor;
this.exercise = exercise;
- this.paths = exercise.getPath();
+ this.exerciseCount = exerciseCount;
+ // TODO: Remove
this.axisVectors = new Vector2f[] {
-
- projectVertex(new Vector3f(-100.0f, 0, 0), getWidth(), getHeight()),
- projectVertex(new Vector3f(100.0f, 0, 0), getWidth(), getHeight()),
- projectVertex(new Vector3f(0, -100.0f, 0), getWidth(), getHeight()),
- projectVertex(new Vector3f(0, 100.0f, 0), getWidth(), getHeight()),
- projectVertex(new Vector3f(0, 0, -100.0f), getWidth(), getHeight()),
- projectVertex(new Vector3f(0, 0, 100.0f), getWidth(), getHeight())
-
+ projectVertex(new Vector3f(-5.0f, 0, 0), getWidth(), getHeight()),
+ projectVertex(new Vector3f(5.0f, 0, 0), getWidth(), getHeight()),
+ projectVertex(new Vector3f(0, -5.0f, 0), getWidth(), getHeight()),
+ projectVertex(new Vector3f(0, 5.0f, 0), getWidth(), getHeight()),
+ projectVertex(new Vector3f(0, 0, -5.0f), getWidth(), getHeight()),
+ projectVertex(new Vector3f(0, 0, 5.0f), getWidth(), getHeight())
};
}
- public void onDestroy()
+ public void destroy()
{
if ( this.motionProcessor != null )
this.motionProcessor.stopListening();
@@ -128,35 +124,55 @@ public class PersonalMotionPreviewElement extends View {
*/
public void provideQiContext(QiContext context) {
this.qiContext = context;
- if ( this.motionProcessor != null )
- this.motionProcessor.stopListening();
-
- this.motionProcessor = new MotionProcessor();
- this.motionProcessor.startListening();
// Handler that is called every time the motion processor receives new data.
- this.motionProcessor.setMotionDataEventHandler((processed, preprocessed, sampleIndex, sampleRate, deviceId) -> {
- int progress = (int)this.motionProcessor.getError(this.paths[0], processed);
- this.exerciseProgress.set(Math.min(1000, Math.max(0, progress)));
- Log.i("MotionProcessor", "Processed data: " + progress + " (" + preprocessed + ")");
- Vector2f parsed = projectVertex(processed, this.getWidth(), this.getHeight());
+ this.motionProcessor.setInputHandler((rotationVector, deviceId) -> {
+
+ Log.i("MotionProcessor", "Rotation vector received: " + rotationVector);
+ Log.i("MotionProcessor", "Last error offset:" + this.motionProcessor.getError(deviceId, this.motionProcessor.secondsPassed()));
+
+ if ( this.motionProcessor.hasFinished())
+ {
+ if ( this.parentActivity == null)
+ {
+ // Move to main screen
+ this.destroy();
+ Intent intent = new Intent(getContext(), MainActivity.class);
+ getContext().startActivity(intent);
+ return;
+ }
+ // Move on to the next exercise, or finish.
+ if ( this.exerciseCount > 0 )
+ {
+ this.exerciseCount--;
+ this.exercise = this.parentActivity.acquireExercise();
+ this.motionProcessor.useExercise(this.exercise);
+ }
+ else
+ {
+ // Finish the exercise.
+ this.destroy();
+ Intent intent = new Intent(getContext(), EndScreenActivity.class);
+ getContext().startActivity(intent);
+ return;
+ }
+ }
+
+ // TODO: Adjust / remove
+ vectors.add(projectVertex(rotationVector, this.getWidth(), this.getHeight()));
+ Log.i("MotionProcessor", "Rotation vector received: " + rotationVector);
+ Vector2f parsed = projectVertex(rotationVector, this.getWidth(), this.getHeight());
+
this.vectors.add(parsed);
// Remove the first element if the array is too big
if (this.vectors.size() > 100)
this.vectors.poll();
});
- saySomethingNice();
- }
- /**
- * Function to say something nice to the user :)
- */
- private void saySomethingNice()
- {
if (this.qiContext == null)
return;
- FitnessCycle.say(USER_PHRASES[(int) Math.floor(Math.random() * USER_PHRASES.length)], this.qiContext);
+ FitnessCycle.say(STARTING_PHRASES[(int) Math.floor(Math.random() * STARTING_PHRASES.length)], this.qiContext);
}
/**
@@ -165,6 +181,7 @@ public class PersonalMotionPreviewElement extends View {
* @param exercise The exercise that the user is currently performing.
*/
public void setExercise(Exercise exercise) {
+ this.motionProcessor.useExercise(exercise);
this.exercise = exercise;
}
@@ -178,12 +195,12 @@ public class PersonalMotionPreviewElement extends View {
// Perspective transformation conserves the depth of the object
projectionMatrix
.identity()
- .perspective((float) Math.toRadians(70), (float) virtualWidth / virtualHeight, .01f, 10000.0f);
+ .perspective((float) Math.toRadians(70), (float) virtualWidth / virtualHeight, .01f, 1000.0f);
// Convert world coordinates to screen-space using MVP matrix
Vector4f screenCoordinates = new Vector4f(point, 1.0f)
- .mul(this.projectionMatrix)
- .mul(this.viewMatrix);
+ .mul(this.viewMatrix)
+ .mul(this.projectionMatrix);
// Normalize screen coordinates from (-1, 1) to (0, virtualWidth) and (0, virtualHeight)
float normalizedX = (screenCoordinates.x / screenCoordinates.w + 1.0f) * 0.5f * virtualWidth;
@@ -195,23 +212,23 @@ public class PersonalMotionPreviewElement extends View {
@Override
public void onDraw(Canvas canvas) {
- canvas.drawRect(0, 0, getWidth(), getHeight(), backgroundColor);
+ canvas.drawRect(0, 0, getWidth(), getHeight(), backgroundPaint);
this.setBackgroundColor(0xFF000000); // Black
/*if (this.exercise == null)
return;*/
for (int i = 0, startX, endX, startY, endY; i < axisVectors.length/2; i++)
{
- startX = (int)Math.max(0, Math.min(this.screenDimensions.x, (int)axisVectors[i*2].x));
- endX = (int)Math.max(0, Math.min(this.screenDimensions.x, (int)axisVectors[i*2+1].x));
- startY = (int)Math.max(0, Math.min(this.screenDimensions.y, (int)axisVectors[i*2].y));
- endY = (int)Math.max(0, Math.min(this.screenDimensions.y, (int)axisVectors[i*2+1].y));
- canvas.drawLine(startX, startY, endX, endY, this.targetPaint);
+ startX = (int)Math.max(0, Math.min(this.getWidth(), (int)axisVectors[i*2].x));
+ endX = (int)Math.max(0, Math.min(this.getWidth(), (int)axisVectors[i*2+1].x));
+ startY = (int)Math.max(0, Math.min(this.getHeight(), (int)axisVectors[i*2].y));
+ endY = (int)Math.max(0, Math.min(this.getHeight(), (int)axisVectors[i*2+1].y));
+ canvas.drawLine(startX, startY, endX, endY, this.borderPaint);
}
for ( Vector2f point : this.vectors)
{
- canvas.drawRect(point.x, point.y, point.x + 5, point.y + 5, this.referencePaint);
+ canvas.drawRect(point.x, point.y, point.x + 5, point.y + 5, this.userProgressPaint);
}
/*
// Draw target circle
@@ -228,7 +245,10 @@ public class PersonalMotionPreviewElement extends View {
);*/
this.invalidate();
+ }
+
+ @Override
+ public void accept(Vector3f rotationVector, int sensorId) {
- timePassed = (System.nanoTime() - startingTime) / 1E9D;
}
}
diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/GesturePath.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/GesturePath.java
index 06ebe10..79b7eaf 100644
--- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/GesturePath.java
+++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/GesturePath.java
@@ -43,6 +43,19 @@ public class GesturePath {
return segments;
}
+ /**
+ * Method for retrieving the vectors of the GesturePath.
+ */
+ public Vector3f[] getVectors()
+ {
+ Vector3f[] vectors = new Vector3f[segments.length + 1];
+ vectors[0] = segments[0].getStart();
+ for ( int i = 0; i < segments.length; i++)
+ vectors[i + 1] = segments[i].getEnd();
+
+ return vectors;
+ }
+
/**
* Method for retrieving the closest path segment to a reference point.
*
@@ -71,48 +84,40 @@ public class GesturePath {
return closest(referencePoint).difference(referencePoint); // Get the closest segment and calculate the error.
}
- // Builder class for the GesturePath object.
- public static class Builder {
- // List of vectors to add to the GesturePath object.
- private final List vectors;
+ /**
+ * Function for converting a string to a GesturePath object.
+ * The input string bytes will be directly converted into 3d vectors.
+ * Every scalar is composed of 32 bits (4 characters), meaning 96 bits per vector.
+ *
+ * Note: ASCII to Vector conversion is done in Big Endian format (most significant byte first).
+ *
+ * @param input The string to convert
+ * @return The GesturePath object
+ */
- /**
- * Constructor for the Builder object.
- *
- * @param vectors The list of vectors to add.
- */
- public Builder(List vectors) {
- this.vectors = vectors;
+ public static GesturePath fromString(String input) {
+ byte[] bytes = input.getBytes();
+
+ // Check if the input string contains a valid amount of bytes (12 bytes per vector)
+ if (input.length() % 12 != 0) {
+ throw new IllegalArgumentException("Invalid input string length");
}
+ Vector3f[] vectors = new Vector3f[input.length() / 12];
- /**
- * Default constructor for the Builder object.
- */
- public Builder() {
- this.vectors = new ArrayList<>();
+ float[] xyz = new float[3];
+ for (int i = 0; i < bytes.length; i += 12) {
+ for (int j = 0; j < 3; j++) {
+
+ xyz[j] = Float.intBitsToFloat(
+ (bytes[i + j * 4] & 0xFF) << 24 |
+ (bytes[i + j * 4 + 1] & 0xFF) << 16 |
+ (bytes[i + j * 4 + 2] & 0xFF) << 8 |
+ (bytes[i + j * 4 + 3] & 0xFF)
+ );
+ }
+ vectors[i / 12] = new Vector3f(xyz[0], xyz[1], xyz[2]);
}
-
- /**
- * Adds a vector to the GesturePath object.
- *
- * @param vector The vector to add.
- * @return The Builder object.
- */
- public Builder addVector(Vector3f vector) {
- vectors.add(vector);
- return this;
- }
-
- /**
- * Builds the GesturePath object.
- *
- * @return The GesturePath object.
- */
- public GesturePath build() {
- return new GesturePath(vectors.toArray(new Vector3f[0]));
- }
-
+ return new GesturePath(vectors);
}
-
}
diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/IInputHandler.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/IInputHandler.java
new file mode 100644
index 0000000..12cb7db
--- /dev/null
+++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/IInputHandler.java
@@ -0,0 +1,14 @@
+package com.example.fitbot.util.processing;
+
+import org.joml.Vector3f;
+
+public interface IInputHandler {
+
+ /**
+ * Function for accepting motion data and the transformed vector.
+ * @param rotationVector The rotation vector of the motion data.
+ * @param sensorId The sensor ID of the motion data.
+ */
+ void accept(Vector3f rotationVector, int sensorId);
+
+}
\ No newline at end of file
diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/IMotionDataConsumer.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/IMotionDataConsumer.java
deleted file mode 100644
index dd3220c..0000000
--- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/IMotionDataConsumer.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.example.fitbot.util.processing;
-
-import org.joml.Vector3f;
-
-public interface IMotionDataConsumer {
-
- /**
- * Function for accepting motion data and the transformed vector.
- * @param transformedVector The transformed vector.
- * @param motionData The input motion data.
- * @param sampleIndex The index of the current sample
- * @param sampleRate The sample rate.
- */
- void accept(Vector3f transformedVector, MotionData motionData, int sampleIndex, double sampleRate, int sensorId);
-
-}
\ No newline at end of file
diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/InputProcessor.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/InputProcessor.java
new file mode 100644
index 0000000..dbca4df
--- /dev/null
+++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/InputProcessor.java
@@ -0,0 +1,236 @@
+package com.example.fitbot.util.processing;
+
+import android.util.Log;
+
+import com.example.fitbot.exercise.Exercise;
+import com.example.fitbot.util.server.WebServer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+import org.jetbrains.annotations.NotNull;
+import org.joml.Vector3f;
+
+public class InputProcessor {
+
+ private Vector3f[][] selfRotationVectorPaths; // Relative path of the motion data
+ private Vector3f[][] targetRotationVectorPaths; // Target path of the motion data
+
+ private final float sampleRate; // The sample rate of the motion sensor
+ private float exerciseDuration;
+
+ // How many seconds have passed since the start of the exercise.
+ // The exercise starts whenever the `startListening` function is called.
+ private double secondsPassed = 0.0D;
+ private long lastTime;
+
+ private IInputHandler motionDataConsumer = (rotationVector, deviceId) -> {
+ };
+ private WebServer server;
+
+
+ /**
+ * Constructor for the motion processor.
+ *
+ * @param paths The target paths of the motion data.
+ * The length of this array must be equal to the
+ * amount of sensors available.
+ * @param inputSampleRate The sample rate of the motion sensor.
+ */
+ public InputProcessor(Vector3f[][] paths, float exerciseTime, float inputSampleRate) {
+ selfRotationVectorPaths = new Vector3f[paths.length][(int) (exerciseTime * inputSampleRate)];
+ targetRotationVectorPaths = paths;
+
+ this.sampleRate = inputSampleRate;
+ this.exerciseDuration = exerciseTime;
+ }
+
+ /**
+ * Function for setting the exercise to use.
+ * This updates the user and target path and the
+ * duration of the exercise.
+ * @param exercise The exercise to use the paths for.
+ */
+ public void useExercise(Exercise exercise) {
+ this.selfRotationVectorPaths = new Vector3f[2][(int) (exercise.exerciseTimeInSeconds * this.sampleRate)];
+ this.targetRotationVectorPaths = new Vector3f[2][exercise.rightPath.getVectors().length];
+ this.exerciseDuration = exercise.exerciseTimeInSeconds;
+ this.secondsPassed = 0.0D;
+ this.lastTime = System.currentTimeMillis();
+ }
+
+ /**
+ * Function for checking if the exercise has finished.
+ * @return True if the exercise has finished, false otherwise.
+ */
+ public boolean hasFinished() {
+ return this.secondsPassed >= this.exerciseDuration;
+ }
+
+ /**
+ * Function for starting the listening process
+ * of the motion sensor. This function will create
+ * a new WebSocket server and start listening for
+ * incoming connections.
+ */
+ public void startListening() {
+ // Create socket server
+ this.server = WebServer.createServer();
+
+ Log.i("MotionProcessor", "Listening for incoming connections.");
+
+ // Check if the socket
+ if (server != null) {
+ // Update event handler to match our functionality.
+ server.setEventHandler(this::parsePacket);
+ this.secondsPassed = 0.0d;
+ this.lastTime = System.currentTimeMillis();
+ }
+ }
+
+ /**
+ * Function for stopping the listening process
+ * of the motion sensor. This function will stop
+ * the WebSocket server.
+ */
+ public void stopListening() {
+ if (server != null) {
+ server.stop();
+ server = null;
+ }
+ }
+
+ /**
+ * Function for parsing arbitrary packet data.
+ *
+ * @param data The data to parse.
+ */
+ public void parsePacket(@NotNull String data) {
+
+ try {
+
+ Log.i("MotionProcessor", "Received packet data: " + data);
+
+ JsonElement json = JsonParser.parseString(data);
+
+ if (!json.isJsonObject())
+ return;
+
+ JsonObject object = json.getAsJsonObject();
+
+ String[] required = {
+ "rotationX", "rotationY", "rotationZ",
+ "type",
+ "deviceId"
+ };
+
+ // Ensure all properties are present in the received JSON object
+ for (String s : required) {
+ if (!object.has(s))
+ return;
+ }
+
+ // Parse the data
+ Vector3f rotation = new Vector3f(object.get("rotationX").getAsFloat(), object.get("rotationY").getAsFloat(), object.get("rotationZ").getAsFloat());
+ int deviceId = object.get("deviceId").getAsInt();
+ String type = object.get("type").getAsString();
+
+ parseRotationVector(rotation, deviceId);
+ } catch (Exception e) {
+ Log.i("MotionProcessor", "Failed to parse packet data.");
+ }
+ }
+
+ /**
+ * Function for adding motion data to the processor.
+ *
+ * @param rotation The rotation vector of the motion data.
+ * @param deviceId The device ID of the motion data.
+ */
+ public void parseRotationVector(Vector3f rotation, int deviceId) {
+ if (deviceId >= 0 && deviceId < selfRotationVectorPaths.length) {
+
+ // Re-calculate time for index calculation
+ secondsPassed = (System.currentTimeMillis() - lastTime) / 1000.0d;
+ lastTime = System.currentTimeMillis();
+
+ // Supposed index of the current rotation vector in the `rotationVectorPaths` array
+ int selfIndex = (int) (secondsPassed * sampleRate);
+
+ motionDataConsumer.accept(rotation, deviceId);
+ if (selfIndex >= selfRotationVectorPaths[deviceId].length || selfIndex < 0)
+ return;
+
+ selfRotationVectorPaths[deviceId][selfIndex] = rotation;
+ }
+ }
+
+ /**
+ * Method for getting the current progress of the exercise.
+ * The return value will range between 0.0 and 1.0.
+ *
+ * @return The current progress of the exercise.
+ */
+ public double getCurrentProgress()
+ {
+ return secondsPassed / exerciseDuration;
+ }
+
+ /**
+ * Function for setting the motion data receiver.
+ *
+ * @param consumer The consumer to set.
+ */
+ public void setInputHandler(IInputHandler consumer) {
+ if (consumer != null)
+ this.motionDataConsumer = consumer;
+ }
+
+ /**
+ * Function for getting the error offsets of the user's path compared to the
+ * target path at a given point in time.
+ *
+ * @param sensorId The sensor ID to get the error offsets from.
+ * @param time The time to get the error offsets from.
+ * This value must be >= 0 && <= exerciseTime, otherwise
+ * the error will be 0 by default.
+ * @return A list of error offsets of the motion data compared to the reference path.
+ */
+ public double getError(int sensorId, float time) {
+
+ // Ensure the sensor ID is within the bounds of the array
+ if (sensorId < 0 || sensorId >= selfRotationVectorPaths.length)
+ return 0.0d;
+
+ // Index of the current rotation vector
+ int targetIndex = (int) ((this.exerciseDuration / this.targetRotationVectorPaths[sensorId].length) * time);
+ int selfIndex = (int) (this.selfRotationVectorPaths[sensorId].length / this.sampleRate * time);
+
+ // Ensure the indexes are within the bounds of the array
+ if (targetIndex >= 0 && targetIndex <= this.targetRotationVectorPaths[sensorId].length - 1 &&
+ selfIndex >= 0 && selfIndex <= this.selfRotationVectorPaths[sensorId].length - 1)
+ {
+ return this.selfRotationVectorPaths[sensorId][selfIndex].distance(this.targetRotationVectorPaths[sensorId][targetIndex]);
+ }
+ return 0.0d;
+ }
+
+ /**
+ * Method for getting the average error of the motion data
+ * compared to the reference path.
+ *
+ * @param sensorId The sensor ID to get the error offsets from.
+ * @return The average error of the motion data compared to the reference path.
+ */
+ public double getAverageError(int sensorId) {
+ double error = 0;
+ for (int i = 0; i < this.exerciseDuration; i++) {
+ error += getError(sensorId, i);
+ }
+ return error / this.exerciseDuration;
+ }
+
+ public float secondsPassed() {
+ return (float) secondsPassed;
+ }
+}
diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionData.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionData.java
deleted file mode 100644
index fae42fd..0000000
--- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionData.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package com.example.fitbot.util.processing;
-
-import org.joml.Vector3f;
-
-import java.util.Objects;
-
-public class MotionData {
-
- // Data of the motion sensor
- public Vector3f acceleration, rotation;
- public int sensorId;
-
- // Delimiter for the data received from the motion sensor
- private static final String DATA_DELIMITER = ";";
-
- /**
- * Constructor for the MotionData class.
- *
- * @param accelerationX The acceleration in the X axis in m/s^2.
- * @param accelerationY The acceleration in the Y axis in m/s^2.
- * @param accelerationZ The acceleration in the Z axis in m/s^2.
- * @param rotationX The rotation in the X axis in degrees.
- * @param rotationY The rotation in the Y axis in degrees.
- * @param rotationZ The rotation in the Z axis in degrees.
- * @param sensorId The sensor id.
- */
- public MotionData(float accelerationX, float accelerationY, float accelerationZ, float rotationX, float rotationY, float rotationZ, int sensorId) {
- this(new Vector3f(accelerationX, accelerationY, accelerationZ), new Vector3f(rotationX, rotationY, rotationZ), sensorId);
- }
-
- /**
- * Constructor for the MotionData class.
- *
- * @param acceleration The acceleration vector in m/s^2.
- * @param rotation The rotation vector in degrees.
- */
- public MotionData(Vector3f acceleration, Vector3f rotation, int sensorId) {
- this.acceleration = acceleration;
- this.rotation = rotation;
- this.sensorId = sensorId;
- }
-
- /**
- * Function for decoding a string into a MotionData object.
- * This string must contain the data of the motion sensor
- * separated by the delimiter. (;)
- *
- * @param data The string containing the data of the motion sensor.
- * @return An instance of MotionData.
- */
- public static MotionData decode(String data) {
- Objects.requireNonNull(data); // Ensure data is not null
-
- String[] parts = data.split(DATA_DELIMITER);
- if (parts.length != 7)
- return null;
-
- return new MotionData(
- Float.parseFloat(parts[0]),
- Float.parseFloat(parts[1]),
- Float.parseFloat(parts[2]),
- Float.parseFloat(parts[3]),
- Float.parseFloat(parts[4]),
- Float.parseFloat(parts[5]),
- Integer.parseInt(parts[6])
- );
- }
-}
diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionProcessor.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionProcessor.java
deleted file mode 100644
index de35e7d..0000000
--- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionProcessor.java
+++ /dev/null
@@ -1,233 +0,0 @@
-package com.example.fitbot.util.processing;
-
-import android.util.Log;
-
-import com.example.fitbot.util.path.GesturePath;
-import com.example.fitbot.util.server.WebServer;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-
-import org.jetbrains.annotations.NotNull;
-import org.joml.Matrix3d;
-import org.joml.Vector3d;
-import org.joml.Vector3f;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-public class MotionProcessor {
-
- public static final String DELIMITER = ";";
-
- private final List relativeLeftPath = new ArrayList<>(); // Relative path of the left motion data
- private final List relativeRightPath = new ArrayList<>(); // Relative path of the motion data
-
- private Vector3f ZERO = new Vector3f(0, 0, 0);
-
- private final float sampleRate = 1.0f / 10.0F; // samples/second
-
- private IMotionDataConsumer motionDataConsumer = (p1, p2, p3, p4, p5) -> { };
- private WebServer server;
-
-
- public MotionProcessor() {}
-
-
- /**
- * Function for starting the listening process
- * of the motion sensor. This function will create
- * a new WebSocket server and start listening for
- * incoming connections.
- */
- public void startListening() {
- // Create socket server
- this.server = WebServer.createServer();
-
- Log.i("MotionProcessor", "Listening for incoming connections.");
-
- // Check if the socket
- if (server != null) {
- // Update event handler to match our functionality.
- server.setEventHandler(this::parsePacket);
- }
- }
-
- /**
- * Function for stopping the listening process
- * of the motion sensor. This function will stop
- * the WebSocket server.
- */
- public void stopListening() {
- if (server != null) {
- server.stop();
- }
- }
-
- /**
- * Function for parsing arbitrary packet data.
- *
- * @param data The data to parse.
- */
- public void parsePacket(@NotNull String data) {
-
- try {
-
- Log.i("MotionProcessor", "Received packet data: " + data);
-
- JsonElement json = JsonParser.parseString(data);
-
- if (!json.isJsonObject())
- return;
-
- JsonObject object = json.getAsJsonObject();
-
- String[] required = {
- "rotationX", "rotationY", "rotationZ",
- "accelerationX", "accelerationY", "accelerationZ",
- "type",
- "deviceId"
- };
-
- // Ensure all properties are present in the received JSON object
- for (String s : required) {
- if (!object.has(s))
- return;
- }
-
- // Parse the data
- Vector3f rotation = new Vector3f(object.get("rotationX").getAsFloat(), object.get("rotationY").getAsFloat(), object.get("rotationZ").getAsFloat());
- Vector3f acceleration = new Vector3f(object.get("accelerationX").getAsFloat(), object.get("accelerationY").getAsFloat(), object.get("accelerationZ").getAsFloat());
- int deviceId = object.get("deviceId").getAsInt();
- String type = object.get("type").getAsString();
- MotionData motionData = new MotionData(rotation, acceleration, deviceId);
-
- if (type.equals("calibrate")) {
- ZERO = getRelativeVector(motionData);
- return;
- }
-
- addMotionData(motionData);
- } catch (Exception e) {
- Log.i("MotionProcessor", "Failed to parse packet data.");
- }
- }
-
- /**
- * Function for adding motion data to the processor.
- *
- * @param data The motion data to add.
- */
- public void addMotionData(MotionData data) {
- List target;
- if (data.sensorId == 0)
- target = relativeLeftPath;
- else target = relativeRightPath;
- Vector3f previous = target.isEmpty() ? ZERO : target.get(target.size() - 1);
- Vector3f relativeVector = getRelativeVector(data).add(previous);
- target.add(relativeVector);
- motionDataConsumer.accept(relativeVector, data, target.size(), this.sampleRate, data.sensorId);
- }
-
- /**
- * Function for updating the relative path.
- *
- * @param relativeRightPath The new relative path.
- */
- public void setRelativePaths(List relativeLeftPath, List relativeRightPath) {
- this.relativeRightPath.clear();
- this.relativeLeftPath.clear();
- this.relativeLeftPath.addAll(relativeLeftPath);
- this.relativeRightPath.addAll(relativeRightPath);
- }
-
- /**
- * Function for setting the motion data receiver.
- *
- * @param consumer The consumer to set.
- */
- public void setMotionDataEventHandler(IMotionDataConsumer consumer) {
- if (consumer != null)
- this.motionDataConsumer = consumer;
- }
-
- /**
- * Function for getting the relative vector of the motion data.
- * This function will calculate the relative position of the motion data
- * based on its acceleration and rotation vectors. This has to be done since
- * the acceleration vector is relative to its own rotation vector.
- *
- * @param motionData The motion data to calculate the relative vector for.
- * @return The relative vector of the motion data.
- */
- public Vector3f getRelativeVector(MotionData motionData) {
-
- // Rotate the acceleration vector back by the rotation vector to make it
- // perpendicular to the gravity vector, then apply double integration to get the relative position.
- // s = 1/2 * a * t^2
- // Step 2: Create rotation matrices for each axis
- // Step 4: Rotate the acceleration vector
-
- return motionData.rotation
- .mul(5);
- /*return motionData.acceleration
- .rotateZ(-motionData.rotation.z)
- .rotateY(-motionData.rotation.y)
- .rotateX(-motionData.rotation.x)
- .mul(sampleRate * sampleRate / 2);*/
- }
-
- /**
- * Function for getting the error offsets of the provided path and the
- * received motion data.
- *
- * @param referencePath The reference path to compare the motion data to.
- * @return A list of error offsets of the motion data compared to the reference path.
- */
- public List getErrors(GesturePath referencePath) {
-
- List errors = new ArrayList<>();
- for (Vector3f vector : relativeRightPath) {
- errors.add(referencePath.getError(vector));
- }
- return errors;
- }
-
- /**
- * Function for getting the error of the motion data compared to the reference path.
- *
- * @param path The path to compare the motion data to.
- * @param referencePoint The reference point to compare the motion data to.
- * @return The error of the motion data compared to the reference path.
- */
- public double getError(GesturePath path, Vector3f referencePoint) {
- return path.getError(referencePoint);
- }
-
- /**
- * Function for calculating the average error of the motion data
- * compared to the reference path.
- *
- * @param referencePath The reference path to compare the motion data to.
- * @return The average error of the motion data compared to the reference path.
- */
- public double getAverageError(GesturePath referencePath, int sensorId) {
- double error = 0;
- for (Double e : getErrors(referencePath)) {
- error += e;
- }
- return error / Math.max(1, (sensorId == 0 ? relativeLeftPath : relativeRightPath).size());
- }
-
- /**
- * Function for logging the statistics of the motion data.
- *
- * @param referencePath The reference path to compare the motion data to.
- */
- public void logStatistics(GesturePath referencePath) {
- Log.i("MotionProcessor", "Path length: " + relativeRightPath.size());
- Log.i("MotionProcessor", "Sample rate: " + sampleRate);
- Log.i("MotionProcessor", "Calibration point: " + ZERO.toString());
- }
-}
diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/server/WebServer.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/server/WebServer.java
index 7fe001a..6eaf4dc 100644
--- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/server/WebServer.java
+++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/server/WebServer.java
@@ -22,7 +22,7 @@ public class WebServer implements Runnable {
protected IWebServerHandler eventHandler = (input) -> {}; // No-op.
private Thread thread;
- private AtomicBoolean forceClose = new AtomicBoolean(false);
+ private final AtomicBoolean forceClose = new AtomicBoolean(false);
/**
* Constructor for creating a new WebSocket server.
diff --git a/code/src/Fitbot/app/src/main/res/drawable/darkred_button_gradient.xml b/code/src/Fitbot/app/src/main/res/drawable/big_red_button_gradient.xml
similarity index 89%
rename from code/src/Fitbot/app/src/main/res/drawable/darkred_button_gradient.xml
rename to code/src/Fitbot/app/src/main/res/drawable/big_red_button_gradient.xml
index 6dce505..878f41e 100644
--- a/code/src/Fitbot/app/src/main/res/drawable/darkred_button_gradient.xml
+++ b/code/src/Fitbot/app/src/main/res/drawable/big_red_button_gradient.xml
@@ -2,11 +2,12 @@
+
\ No newline at end of file
diff --git a/code/src/Fitbot/app/src/main/res/drawable/red_button_gradient.xml b/code/src/Fitbot/app/src/main/res/drawable/red_button_gradient.xml
index a087a43..6983a91 100644
--- a/code/src/Fitbot/app/src/main/res/drawable/red_button_gradient.xml
+++ b/code/src/Fitbot/app/src/main/res/drawable/red_button_gradient.xml
@@ -2,7 +2,7 @@
@@ -34,7 +36,7 @@
style="@style/TextStyleTitle"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:text="@string/klaar" />
+ android:text="@string/afgerond" />
+ android:text="@string/voltooid"
+ android:textAlignment="center"/>
diff --git a/code/src/Fitbot/app/src/main/res/layout/activity_fitness.xml b/code/src/Fitbot/app/src/main/res/layout/activity_fitness.xml
index 4d3e7f8..a619a72 100644
--- a/code/src/Fitbot/app/src/main/res/layout/activity_fitness.xml
+++ b/code/src/Fitbot/app/src/main/res/layout/activity_fitness.xml
@@ -45,6 +45,7 @@ tools:openDrawer="start">
android:layout_width="150dp"
android:layout_height="75dp"
android:layout_marginStart="404dp"
+ android:layout_marginBottom="30dp"
android:background="@drawable/red_button_gradient"
android:drawableTop="@drawable/ic_baseline_home_48"
android:drawableTint="@color/white"
diff --git a/code/src/Fitbot/app/src/main/res/layout/activity_help.xml b/code/src/Fitbot/app/src/main/res/layout/activity_help.xml
index 72e7ff7..369397f 100644
--- a/code/src/Fitbot/app/src/main/res/layout/activity_help.xml
+++ b/code/src/Fitbot/app/src/main/res/layout/activity_help.xml
@@ -5,19 +5,22 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/darkBlue"
+ android:fitsSystemWindows="true"
tools:context=".ui.activities.HelpActivity">
@@ -66,7 +71,8 @@
style="@style/TextStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:text="@string/uitlegStart" />
+ android:text="@string/uitlegStart"
+ android:textAlignment="center"/>
+ android:text="@string/uitlegHome"
+ android:textAlignment="center"/>
diff --git a/code/src/Fitbot/app/src/main/res/layout/activity_main.xml b/code/src/Fitbot/app/src/main/res/layout/activity_main.xml
index bdd2cac..0a7c0e1 100644
--- a/code/src/Fitbot/app/src/main/res/layout/activity_main.xml
+++ b/code/src/Fitbot/app/src/main/res/layout/activity_main.xml
@@ -27,11 +27,15 @@
@@ -55,44 +59,60 @@
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="0.727" />
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="0.258" />
diff --git a/code/src/Fitbot/app/src/main/res/values/strings.xml b/code/src/Fitbot/app/src/main/res/values/strings.xml
index 2983a9a..cdc20ab 100644
--- a/code/src/Fitbot/app/src/main/res/values/strings.xml
+++ b/code/src/Fitbot/app/src/main/res/values/strings.xml
@@ -19,8 +19,8 @@
Druk op Start om de oefening te beginnen
Ga terug naar het begin scherm door op het huisje te klikken
- Klaar!
- U heeft de oefeningen voltooid, druk op start om nog een sessie te beginnen
+ Oefeningen afgerond
+ U heeft de oefeningen voltooid! \n Druk op start om nog een sessie te beginnen
Score:
\ No newline at end of file
diff --git a/code/src/Fitbot/app/src/test/java/com/example/fitbot/DatabaseFetchingTest.java b/code/src/Fitbot/app/src/test/java/com/example/fitbot/DatabaseFetchingTest.java
new file mode 100644
index 0000000..9c824d8
--- /dev/null
+++ b/code/src/Fitbot/app/src/test/java/com/example/fitbot/DatabaseFetchingTest.java
@@ -0,0 +1,29 @@
+package com.example.fitbot;
+
+import com.example.fitbot.exercise.EMuscleGroup;
+import com.example.fitbot.exercise.Exercise;
+import com.example.fitbot.exercise.ExerciseManager;
+
+import org.junit.Test;
+
+public class DatabaseFetchingTest {
+
+
+ @Test
+ public void testDatabaseFetching() {
+ Exercise exercise = ExerciseManager.retrieveExercise();
+ assert exercise != null;
+ System.out.println("\n---------------------------------");
+ System.out.println("Exercise:");
+ System.out.println("Name: " + exercise.title);
+ System.out.println("Description: " + exercise.description);
+ System.out.println("Muscle Group: " + exercise.muscleGroup);
+ System.out.println("Image URL: " + exercise.imageUrl);
+ System.out.println("Video URL: " + exercise.videoUrl);
+ System.out.println("Exercise Time: " + exercise.exerciseTimeInSeconds);
+ System.out.println("\n---------------------------------");
+
+ }
+
+
+}