diff --git a/code/arduino/Movement-sensor-code/Connectivity.cpp b/code/arduino/Movement-sensor-code/Connectivity.cpp index bfc3c04..5bdaa9e 100644 --- a/code/arduino/Movement-sensor-code/Connectivity.cpp +++ b/code/arduino/Movement-sensor-code/Connectivity.cpp @@ -21,9 +21,10 @@ void Connectivity::connectWiFi(char* ssid, char* pass){ // } const char* getServerURL = "http://145.92.8.132:443/get-ip"; -String ipAddress = ""; +String ipAddress = ""; // string that will hold the server's IP address -String Connectivity::fetchIPAddress() { +const char* Connectivity::fetchIPAddress() { + char* ipAddress = NULL; // Declare ipAddress as a char* if (WiFi.status() == WL_CONNECTED) { HTTPClient http; WiFiClient client; @@ -32,7 +33,12 @@ String Connectivity::fetchIPAddress() { int httpCode = http.GET(); if (httpCode > 0) { if (httpCode == HTTP_CODE_OK) { - ipAddress = http.getString(); + // If successful (code 200), read the response body and parse the IP address + String response = http.getString(); + StaticJsonDocument<200> doc; + deserializeJson(doc, response); + const char* ip = doc["ip"]; // Extract the IP address + ipAddress = strdup(ip); } } else { Serial.printf("GET request failed, error: %s\n", http.errorToString(httpCode).c_str()); diff --git a/code/arduino/Movement-sensor-code/Connectivity.h b/code/arduino/Movement-sensor-code/Connectivity.h index 6de0109..27cfe94 100644 --- a/code/arduino/Movement-sensor-code/Connectivity.h +++ b/code/arduino/Movement-sensor-code/Connectivity.h @@ -11,6 +11,8 @@ #include #include #include +#include + class Connectivity { @@ -19,7 +21,7 @@ public: void websocketSetup(char* ip, uint16_t port, char* adress); void sendData(float roll, float pitch, float yaw); int httpPost(const char *serverAddress, const char *serverSubPath, const unsigned short serverPort, const char *data, const size_t dataLength, const char *contentType); - String fetchIPAddress(); + const char* fetchIPAddress(); private: ESP8266WiFiMulti wifi; diff --git a/code/arduino/Movement-sensor-code/Movement-sensor-code.ino b/code/arduino/Movement-sensor-code/Movement-sensor-code.ino index 061f12d..4a17a93 100644 --- a/code/arduino/Movement-sensor-code/Movement-sensor-code.ino +++ b/code/arduino/Movement-sensor-code/Movement-sensor-code.ino @@ -4,13 +4,12 @@ void setup() { //connect to internet and start sensor connectivity.connectWiFi(ssid, pass); sensorManager.sensorSetup(); - Serial.begin(9600); + Serial.begin(115200); + Serial.println("startup"); } -unsigned long lastTime = 0; // will store the last time the code was run - void loop() { - SensorManager::eulerAngles eulerRotation = sensorManager.getEulerAngles(); + SensorManager::RotationQuaternions Rotation = sensorManager.getQuaternions(); // SensorManager::acceleration rotationAcceleration = sensorManager.getAcelleration(); struct acceleration { @@ -19,6 +18,12 @@ struct acceleration { float z = 9; } accelData; + if (!ipAquired) { + serverIp = connectivity.fetchIPAddress(); // Assign the value here + ipAquired = true; + } + + unsigned long lastTime = 0; // will store the last time the code was run unsigned long currentTime = millis(); if (currentTime - lastTime >= 100) { // 100 ms has passed memset(buffer, 0, BUFFER_SIZE); @@ -26,18 +31,20 @@ struct acceleration { buffer, "{\"deviceId\": %d, \"rotationX\": %f, \"rotationY\": %f, \"rotationZ\": %f, \"accelerationX\": %f, \"accelerationY\": %f, \"accelerationZ\": %f, \"type\": %s}", DEVICE_ID, - eulerRotation.roll, - eulerRotation.pitch, - eulerRotation.yaw, - accelData.x, + Rotation.i, + Rotation.j, + Rotation.k, + Rotation.w, accelData.y, accelData.z, "data"); // %d = int, %f = floatation, %s = string - connectivity.httpPost(connectivity.fetchIPAddress(), "/", 3445, buffer, strlen(buffer), "application/json"); + connectivity.httpPost(serverIp, "/", 3445, buffer, strlen(buffer), "application/json"); + Serial.println(serverIp); + Serial.println(buffer); lastTime = currentTime; } } //acceleration.X //acceleration.Y -//acceleration.Z +//acceleration.Z \ No newline at end of file diff --git a/code/arduino/Movement-sensor-code/SensorManager.cpp b/code/arduino/Movement-sensor-code/SensorManager.cpp index 7fee39d..ab2216f 100644 --- a/code/arduino/Movement-sensor-code/SensorManager.cpp +++ b/code/arduino/Movement-sensor-code/SensorManager.cpp @@ -17,14 +17,14 @@ void SensorManager::sensorSetup() { // myIMU.enableStepCounter(500); //Send data update every 500ms } //get sensordata -SensorManager::RotationQuintillions SensorManager::getQuintillions() { +SensorManager::RotationQuaternions SensorManager::getQuaternions() { if (myIMU.dataAvailable() == true) { float i = myIMU.getQuatI(); float j = myIMU.getQuatJ(); float k = myIMU.getQuatK(); float w = myIMU.getQuatReal(); - RotationQuintillions rotation = { i, j, k, w }; + RotationQuaternions rotation = { i, j, k, w }; return rotation; } else { float i = myIMU.getQuatI(); @@ -32,13 +32,13 @@ SensorManager::RotationQuintillions SensorManager::getQuintillions() { float k = myIMU.getQuatK(); float w = myIMU.getQuatReal(); - RotationQuintillions rotation = { i, j, k, w }; + RotationQuaternions rotation = { i, j, k, w }; return rotation; } } -//calculate Quintillions to Euler angles from -1π to +1π +//calculate Quaternions to Euler angles from -1π to +1π SensorManager::eulerAngles SensorManager::getEulerAngles() { - SensorManager::RotationQuintillions rotation = getQuintillions(); + SensorManager::RotationQuaternions rotation = getQuaternions(); float roll = atan2(2.0f * (rotation.w * rotation.i + rotation.j * rotation.k), 1.0f - 2.0f * (rotation.i * rotation.i + rotation.j * rotation.j)); float pitch = asin(2.0f * (rotation.w * rotation.j - rotation.k * rotation.i)); float yaw = atan2(2.0f * (rotation.w * rotation.k + rotation.i * rotation.j), 1.0f - 2.0f * (rotation.j * rotation.j + rotation.k * rotation.k)); diff --git a/code/arduino/Movement-sensor-code/SensorManager.h b/code/arduino/Movement-sensor-code/SensorManager.h index aaf4d71..d1a0e4e 100644 --- a/code/arduino/Movement-sensor-code/SensorManager.h +++ b/code/arduino/Movement-sensor-code/SensorManager.h @@ -22,14 +22,17 @@ public: eulerAngles getEulerAngles(); acceleration getAcelleration(); bool sensorTap(); -private: - struct RotationQuintillions { + + struct RotationQuaternions { float i; float j; float k; float w; }; - RotationQuintillions getQuintillions(); + RotationQuaternions getQuaternions(); + +private: + BNO080 myIMU; }; diff --git a/code/arduino/Movement-sensor-code/headerFIle.h b/code/arduino/Movement-sensor-code/headerFIle.h index 8e3480e..cc25dec 100644 --- a/code/arduino/Movement-sensor-code/headerFIle.h +++ b/code/arduino/Movement-sensor-code/headerFIle.h @@ -8,10 +8,12 @@ Connectivity connectivity; WebSocketsClient webSocket; #define USE_SERIAL Serial -#define ssid "1235678i" -#define pass "12345678" +#define ssid "msi 5556" +#define pass "abc12345" #define BUFFER_SIZE 1024 #define DEVICE_ID 1 #define IP_ADDRESS "192.168.137.12" char *buffer = (char *)malloc(sizeof(char) * BUFFER_SIZE); +const char* serverIp = NULL; // Declare serverIp here +bool ipAquired = false; \ No newline at end of file diff --git a/code/server/incoming_request_handlers.js b/code/server/incoming_request_handlers.js index ef583a1..9d23366 100644 --- a/code/server/incoming_request_handlers.js +++ b/code/server/incoming_request_handlers.js @@ -1,5 +1,5 @@ - -const databaseQuery = 'SELECT * FROM `Exercise` ORDER BY RAND() LIMIT 1'; +const databaseQuery = 'SELECT * FROM `Exercise` WHERE ExerciseID = 1'; +// const databaseQueryRand = 'SELECT * FROM `Exercise` ORDER BY RAND() LIMIT 1'; /** * diff --git a/code/src/Fitbot/.gitignore b/code/src/Fitbot/.gitignore index 732c947..203dde6 100644 --- a/code/src/Fitbot/.gitignore +++ b/code/src/Fitbot/.gitignore @@ -14,4 +14,5 @@ .cxx local.properties .idea -.vscode \ No newline at end of file +.vscode +/.idea/ diff --git a/code/src/Fitbot/.idea/misc.xml b/code/src/Fitbot/.idea/misc.xml index b1965d6..ca880a9 100644 --- a/code/src/Fitbot/.idea/misc.xml +++ b/code/src/Fitbot/.idea/misc.xml @@ -25,6 +25,7 @@ + @@ -35,7 +36,7 @@ - + @@ -47,6 +48,7 @@ + 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 331d356..18153d7 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 @@ -7,7 +7,8 @@ public class Exercise { public final EMuscleGroup muscleGroup; public final GesturePath leftPath; public final GesturePath rightPath; - public final String title; + public final String name; + public final String shortDescription; public final String description; public final String imageUrl; public final String videoUrl; @@ -16,17 +17,19 @@ public class Exercise { /** * Constructor for the AbstractExercise class. * - * @param muscleGroup The muscle group of the exercise. - * @param leftPath The path of the left hand. - * @param rightPath The path of the right hand. - * @param title The title of the exercise. - * @param description The description of the exercise. - * @param imageUrl The URL of the image. - * @param videoUrl The URL of the video. + * @param muscleGroup The muscle group of the exercise. + * @param leftPath The path of the left hand. + * @param rightPath The path of the right hand. + * @param name The title of the exercise. + * @param shortDescription The short description of the exercise. + * @param description The full description of the exercise. + * @param imageUrl The URL of the image. + * @param videoUrl The URL of the video. */ - public Exercise(EMuscleGroup muscleGroup, String title, String description, String imageUrl, String videoUrl, GesturePath leftPath, GesturePath rightPath, float exerciseTimeInSeconds) { + public Exercise(EMuscleGroup muscleGroup, String name,String shortDescription, String description, String imageUrl, String videoUrl, GesturePath leftPath, GesturePath rightPath, float exerciseTimeInSeconds) { + this.name = name; this.muscleGroup = muscleGroup; - this.title = title; + this.shortDescription = shortDescription; this.description = description; this.leftPath = leftPath; this.rightPath = rightPath; 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 75b11ae..0de72d0 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 @@ -13,7 +13,7 @@ import java.net.URLConnection; public class ExerciseManager { - private static final String HOST_ADDRESS = "http://145.92.8.132:443/"; + 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. @@ -21,23 +21,22 @@ public class ExerciseManager { private static final String PROPERTY_EXERCISE_DURATION = "duration"; private static final String PROPERTY_EXERCISE_ID = "exerciseId"; private static final String PROPERTY_MUSCLE_GROUP = "muscleGroup"; - private static final String PROPERTY_SHORT_DESC = "shortDescription"; private static final String PROPERTY_IMAGE_URL = "imageUrl"; private static final String PROPERTY_VIDEO_URL = "videoUrl"; private static final String PROPERTY_DESC = "description"; + private static final String PROPERTY_SHORT_DESC = "shortDescription"; private static final String PROPERTY_PATH = "path"; private static final String PROPERTY_NAME = "name"; // The delimiter used to separate the paths of the sensors. - public static final String PATH_DELIMITER = ";"; + public static final String PATH_DELIMITER = ":"; public static final int SENSOR_COUNT = 2; private static final String[] REQUIRED_PROPERTIES = { - PROPERTY_MUSCLE_GROUP, PROPERTY_DESC, PROPERTY_IMAGE_URL, + PROPERTY_MUSCLE_GROUP, PROPERTY_DESC,PROPERTY_SHORT_DESC, PROPERTY_IMAGE_URL, PROPERTY_VIDEO_URL, PROPERTY_NAME, PROPERTY_PATH, - PROPERTY_EXERCISE_DURATION, PROPERTY_EXERCISE_ID, - PROPERTY_SHORT_DESC + PROPERTY_EXERCISE_DURATION, PROPERTY_EXERCISE_ID }; public static final int DEFAULT_EXERCISE_REPETITIONS = 10; @@ -85,16 +84,18 @@ public class ExerciseManager { */ public static Exercise fetchExerciseFromDatabase() { String response = sendHTTP( - HOST_ADDRESS, "POST", "application/json", + HOST_ADDRESS, "POST", "application/json", "{}" ); // Validate the response if (response != null) { try { + System.out.println(response); JsonObject content = JsonParser.parseString(response).getAsJsonObject(); // Ensure all required properties are present for (String property : REQUIRED_PROPERTIES) { if (!content.has(property)) { + System.out.println("Missing property: " + property); return null; } } @@ -102,14 +103,18 @@ public class ExerciseManager { // 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. + System.out.println(content.get(PROPERTY_PATH).getAsString()); String[] leftRightData = content.get(PROPERTY_PATH).getAsString().split(PATH_DELIMITER); - if (leftRightData.length != SENSOR_COUNT) + if (leftRightData.length != SENSOR_COUNT) { + System.out.println("Invalid path data."); return null; + } return new Exercise( EMuscleGroup.parse(content.get(PROPERTY_MUSCLE_GROUP).getAsString()), content.get(PROPERTY_NAME).getAsString(), + content.get(PROPERTY_SHORT_DESC).getAsString(), content.get(PROPERTY_DESC).getAsString(), content.get(PROPERTY_IMAGE_URL).getAsString(), content.get(PROPERTY_VIDEO_URL).getAsString(), 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 abde556..7282278 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 @@ -3,9 +3,11 @@ package com.example.fitbot.ui.activities; import android.app.Dialog; import android.content.Context; import android.graphics.drawable.ColorDrawable; +import android.media.MediaPlayer; import android.net.Uri; import android.os.Bundle; import android.util.Log; +import android.widget.TextView; import android.view.View; import android.view.WindowManager; import android.widget.Button; @@ -29,10 +31,19 @@ import org.joml.Vector3f; public class FitnessActivity extends RobotActivity implements RobotLifecycleCallbacks { // Private fields for the FitnessActivity class. - private ExerciseStatusElement personalMotionPreviewElement; + private ExerciseStatusElement exerciseStatusElement; private InputProcessor motionProcessor; private Exercise currentExercise; + // Exercise status element data + private TextView exerciseMuscleGroupTextView; + private TextView exerciseNameTextView; + private TextView exerciseShortDescriptionTextView; + //private TextView exerciseDescriptionTextView; + private static String exerciseVideoUrl; + + private final Object lock = new Object(); + // 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.", @@ -45,26 +56,29 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall private static final float SENSOR_SAMPLE_RATE = 10.0f; private static final int EXERCISE_COUNT = 5; + private static int EXERCISE_REP = 10; 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 - setSpeechBarDisplayStrategy(SpeechBarDisplayStrategy.IMMERSIVE); + // Fill empty objects with exercise data + this.exerciseNameTextView = findViewById(R.id.textViewFitnessTitle); + this.exerciseShortDescriptionTextView = findViewById(R.id.textViewFitnessShortDescription); + //this.exerciseDescriptionTextView = findViewById(R.id.textViewDialogDescription); - // Find the VideoView by its ID - VideoView videoView = findViewById(R.id.videoView); - playVideo(videoView, this); + // Navigation Buttons NavigationManager.setupButtonNavigation(this, R.id.homeButtonFitness, MainActivity.class); NavigationManager.setupButtonNavigation(this, R.id.skipButtonFitness, MainActivity.class); //Needs to skip exercises once those are implemented + // Hide system UI NavigationManager.hideSystemUI(this); + setSpeechBarDisplayStrategy(SpeechBarDisplayStrategy.IMMERSIVE); + // Initiate info button Button infoButtonFitness = findViewById(R.id.infoButtonFitness); infoButtonFitness.setOnClickListener(new View.OnClickListener() { @Override @@ -80,22 +94,24 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall // Provide the context so that all queued actions can be performed. Pepper.provideContext(qiContext, this.getClass()); - personalMotionPreviewElement = findViewById(R.id.personalMotionPreviewElement); + exerciseStatusElement = 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(() -> { + exerciseStatusElement.post(() -> { this.fetchExerciseAsync((exercise) -> { // 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.initialize(exercise, motionProcessor, EXERCISE_COUNT); - + exerciseStatusElement.initialize(exercise, motionProcessor, EXERCISE_COUNT); + motionProcessor.useExercise(exercise); + /* TODO: Remove if not needed */motionProcessor.setRecording(true, 10); + motionProcessor.setInputHandler(exerciseStatusElement); motionProcessor.startListening(); - motionProcessor.setInputHandler(personalMotionPreviewElement); + }, (n) -> { int randomMessageIndex = (int) Math.floor(Math.random() * EXERCISE_NOT_FOUND_MESSAGES.length); Pepper.say(EXERCISE_NOT_FOUND_MESSAGES[randomMessageIndex]); @@ -103,8 +119,6 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall NavigationManager.navigateToActivity(this, EndScreenActivity.class); }); }); - - // FitnessCycle.playVideo(qiContext, videoView, this); } /** @@ -121,6 +135,27 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall onFailedFetch.handle(null); } else { onSuccessfulFetch.handle(exercise); + this.runOnUiThread(() -> { + exerciseNameTextView.setText(exercise.name); + exerciseShortDescriptionTextView.setText(exercise.shortDescription); + // exerciseDescriptionTextView.setText(exercise.description); + exerciseVideoUrl = exercise.videoUrl; + + // Play the video + VideoView videoView = findViewById(R.id.videoView); + playVideo(videoView, this); + + // Set a listener to repeat the video + while (EXERCISE_REP > 1) { + videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mp) { + videoView.start(); // start the video again + } + }); + EXERCISE_REP--; + } + }); } })).start(); } @@ -134,14 +169,7 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall public static void playVideo(VideoView videoView, Context context) { // Set up the video player if (videoView != null) { - Uri videoUri = Uri.parse("android.resource://" + context.getPackageName() + "/" + R.raw.bicepvideo); - videoView.setVideoURI(videoUri); - - videoView.setOnCompletionListener(mp -> { - // Repeat the video when it finishes playing - videoView.start(); - }); - + videoView.setVideoPath(exerciseVideoUrl); videoView.start(); } else { Log.e("FitnessActivity", "VideoView is null. Check your layout XML."); @@ -154,7 +182,7 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall Pepper.provideContext(null, this.getClass()); // Remove the context (unavailable) // Go to the main screen - NavigationManager.navigateToActivity(this, MainActivity.class); +// NavigationManager.navigateToActivity(this, MainActivity.class); } @Override @@ -177,12 +205,15 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall final Dialog dialog = new Dialog(this); dialog.setContentView(R.layout.dialog_info); + // Hide the system UI NavigationManager.hideSystemUI(this); + // Set the dialog options dialog.getWindow().setDimAmount(0.5f); dialog.getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT)); dialog.setCancelable(true); + // Close button for dialog Button closeButton = dialog.findViewById(R.id.closeButtonDialog); closeButton.setOnClickListener(new View.OnClickListener() { @Override diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/ExerciseStatusElement.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/ExerciseStatusElement.java index 606321b..293068c 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/ExerciseStatusElement.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/ExerciseStatusElement.java @@ -18,12 +18,7 @@ import com.example.fitbot.util.NavigationManager; import com.example.fitbot.util.processing.IInputHandler; import com.example.fitbot.util.processing.InputProcessor; -import org.joml.Matrix4f; -import org.joml.Vector2f; import org.joml.Vector3f; -import org.joml.Vector4f; - -import java.util.concurrent.ConcurrentLinkedQueue; public class ExerciseStatusElement extends View implements IInputHandler { @@ -38,16 +33,6 @@ public class ExerciseStatusElement extends View implements IInputHandler { private final Paint borderPaint = new Paint(); private final Paint backgroundPaint = new Paint(); - // TODO: Remove - private final Matrix4f viewMatrix = new Matrix4f(); // The view matrix for the 3D to 2D transformation. - private final 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 final ConcurrentLinkedQueue vectors = new ConcurrentLinkedQueue<>(); - - // TODO: Remove - private Vector2f[] axisVectors = new Vector2f[0]; - - private static final String[] STARTING_PHRASES = { "Veel success met de oefening!", "Je kan het!", @@ -93,60 +78,10 @@ public class ExerciseStatusElement extends View implements IInputHandler { this.exercise = exercise; this.exerciseCount = exerciseCount; - /* TODO: Remove */ - this.axisVectors = new Vector2f[]{ - 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()) - }; - Pepper.say(STARTING_PHRASES[(int) Math.floor(Math.random() * STARTING_PHRASES.length)]); // Handler that is called every time the motion processor receives new data. - 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 for some reason the parent activity is not defined, - // move back to the main screen. - if (this.parentActivity == null) { - // Move to main screen - NavigationManager.navigateToActivity(getContext(), MainActivity.class); - return; - } - // Move on to the next exercise, or finish. - if (this.exerciseCount > 0) { - this.exerciseCount--; - this.parentActivity.fetchExerciseAsync((newExercise) -> { - this.motionProcessor.useExercise(newExercise); - }, (failed) -> { - // Move to main screen - NavigationManager.navigateToActivity(parentActivity, MainActivity.class); - }); - } else { - // Finish the exercise. - NavigationManager.navigateToActivity(parentActivity, EndScreenActivity.class); - return; - } - } - - /* TODO: Use raw vector */ - vectors.add(projectVertex(rotationVector, this.getWidth(), this.getHeight())); - - /* TODO: Remove */ - Vector2f parsed = projectVertex(rotationVector, this.getWidth(), this.getHeight()); - - this.vectors.add(parsed /* TODO: Add regular vertex once exercise data is stored in DB*/); - - // Remove the first element if the array is too big - if (this.vectors.size() > 100) - this.vectors.poll(); - }); } /** @@ -157,52 +92,15 @@ public class ExerciseStatusElement extends View implements IInputHandler { public void setExercise(Exercise exercise) { this.motionProcessor.useExercise(exercise); this.exercise = exercise; + Log.i("MotionProcessor", "Updating exercise in ExerciseStatusElement"); } - - private Vector2f projectVertex(Vector3f point, int virtualWidth, int virtualHeight) { - viewMatrix - .identity() - .lookAt(new Vector3f(0, 0, -2), new Vector3f(0, 0, 0), new Vector3f(0, 1, 0)); - - // Transform the projection matrix to a perspective projection matrix - // Perspective transformation conserves the depth of the object - projectionMatrix - .identity() - .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.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; - float normalizedY = (1.0f - screenCoordinates.y / screenCoordinates.w) * 0.5f * virtualHeight; - return new Vector2f(normalizedX, normalizedY); - } - - @Override public void onDraw(Canvas canvas) { canvas.drawRect(0, 0, getWidth(), getHeight(), backgroundPaint); this.setBackgroundColor(0xFF000000); // Black /*if (this.exercise == null) return;*/ - - - /* TODO: Remove */ - for (int i = 0, startX, endX, startY, endY; i < axisVectors.length / 2; i++) { - 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.userProgressPaint); - } /* // Draw target circle float targetRadius = (this.screenDimensions.x + this.screenDimensions.y) / 5.0f; @@ -222,6 +120,40 @@ public class ExerciseStatusElement extends View implements IInputHandler { @Override public void accept(Vector3f rotationVector, int sensorId) { + Log.i("MotionProcessor", "Rotation vector received: " + rotationVector); + Log.i("MotionProcessor", "Last error offset:" + this.motionProcessor.getError(sensorId, this.motionProcessor.secondsPassed())); + + // Check whether the current exercise has been completed. + // This is determined by the duration of the exercise, and the amount of time that has passed. + // The duration of the exercise originates from the database, and is stored in seconds. + // Whenever 'useExercise' is called, the timer resets and this method will be called again. + if (this.motionProcessor.hasFinished() && !this.motionProcessor.isRecording()) { + // If for some reason the parent activity is not defined, + // move back to the main screen. + if (this.parentActivity == null) { + // Move to main screen + Log.i("MotionProcessor", "Parent activity was null."); + NavigationManager.navigateToActivity(getContext(), MainActivity.class); + return; + } + // Move on to the next exercise, or finish. + if (this.exerciseCount > 0) { + this.exerciseCount--; + this.parentActivity.fetchExerciseAsync((newExercise) -> { + this.motionProcessor.useExercise(newExercise); + + // Whenever the database retrieval failed, we return to the main screen. + }, (failed) -> { + // Move to main screen + Log.i("MotionProcessor", "Failed to fetch exercise from database"); + NavigationManager.navigateToActivity(parentActivity, MainActivity.class); + }); + } else { + // Finish the exercise. + Log.i("MotionProcessor", "Exercise has finished"); + NavigationManager.navigateToActivity(parentActivity, EndScreenActivity.class); + } + } } } 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 14e7706..f3b8cb3 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 @@ -13,8 +13,14 @@ public class GesturePath { * @param vectors The vectors that make up the path. */ public GesturePath(Vector3f[] vectors) { - if (vectors.length < 2) - throw new IllegalArgumentException("A path must have at least two points."); + if (vectors.length < 2) { + this.segments = new PathSegment[1]; + this.segments[0] = new PathSegment( + new Vector3f(0, 0, 0), + new Vector3f(0, 0, 0) + ); + return; + } this.segments = new PathSegment[vectors.length - 1]; for (int i = 0; i < vectors.length - 1; i++) @@ -92,11 +98,13 @@ public class GesturePath { */ public static GesturePath fromString(String input) { + if (input == null) + throw new IllegalArgumentException("Input string cannot be null"); 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"); + throw new IllegalArgumentException("Invalid input string length (" + input.length() + " bytes provided - must be a multiple of 12)"); } Vector3f[] vectors = new Vector3f[input.length() / 12]; 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 index c78bd89..e95008a 100644 --- 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 @@ -80,6 +80,8 @@ public class InputProcessor { this.selfRotationVectorPaths = new Vector3f[2][(int) (exercise.exerciseTimeInSeconds * this.sampleRate)]; this.targetRotationVectorPaths = new Vector3f[2][exercise.rightPath.getVectors().length]; + this.targetRotationVectorPaths[0] = exercise.leftPath.getVectors(); + this.targetRotationVectorPaths[1] = exercise.rightPath.getVectors(); this.exerciseDurationInSeconds = exercise.exerciseTimeInSeconds; this.secondsPassed = 0.0D; this.lastTime = System.currentTimeMillis(); @@ -99,7 +101,7 @@ public class InputProcessor { if (recording) { this.secondsPassed = 0.0D; this.lastTime = System.currentTimeMillis(); - + this.selfRotationVectorPaths = new Vector3f[2][(int) (duration * this.sampleRate)]; } } @@ -158,6 +160,8 @@ public class InputProcessor { try { + Log.i("MotionProcessor", "Time passed: " + this.secondsPassed + "s"); + Log.i("MotionProcessor", "Recording: " + this.recordingMovement + ", " + this.secondsPassed + " / " + this.recordingDurationInSeconds); Log.i("MotionProcessor", "Received packet data: " + data); JsonElement json = JsonParser.parseString(data); @@ -178,6 +182,7 @@ public class InputProcessor { int deviceId = object.get("deviceId").getAsInt(); String type = object.get("type").getAsString(); + // Parse the retrieved data parseRotationVector(rotation, deviceId); } catch (Exception e) { Log.i("MotionProcessor", "Failed to parse packet data."); @@ -195,18 +200,12 @@ public class InputProcessor { // 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; - - if ( this.recordingMovement && this.hasFinished()) { + if ( this.recordingMovement && this.secondsPassed >= this.recordingDurationInSeconds) { // Do something with the recorded data. this.recordingMovement = false; // Convert recorded data from `selfRotationVectorPaths` to string, and @@ -218,6 +217,11 @@ public class InputProcessor { Log.i("MotionProcessor", "Converted data: "); Log.i("MotionProcessor", converted); } + motionDataConsumer.accept(rotation, deviceId); + if (selfIndex >= selfRotationVectorPaths[deviceId].length || selfIndex < 0) + return; + + selfRotationVectorPaths[deviceId][selfIndex] = rotation; } } @@ -334,4 +338,8 @@ public class InputProcessor { public float secondsPassed() { return (float) secondsPassed; } + + public boolean isRecording() { + return this.recordingMovement; + } } diff --git a/code/src/Fitbot/app/src/main/res/drawable/fitbot_launcher_background.xml b/code/src/Fitbot/app/src/main/res/drawable/fitbot_launcher_background.xml index ca3826a..071825e 100644 --- a/code/src/Fitbot/app/src/main/res/drawable/fitbot_launcher_background.xml +++ b/code/src/Fitbot/app/src/main/res/drawable/fitbot_launcher_background.xml @@ -5,7 +5,7 @@ android:viewportHeight="108" android:viewportWidth="108" xmlns:android="http://schemas.android.com/apk/res/android"> - 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 d72d83c..f802f04 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 @@ -42,12 +42,23 @@ android:layout_margin="10dp" tools:ignore="SpeakableTextPresentCheck" /> - + android:layout_marginStart="50dp" + android:layout_marginTop="50dp" + android:layout_marginEnd="50dp" + android:layout_marginBottom="50dp" + card_view:cardCornerRadius="30dp"> + + + + @@ -58,9 +69,9 @@ android:layout_marginVertical="20dp" android:layout_marginLeft="15dp" android:layout_marginRight="30dp" + android:padding="10dp" android:background="@drawable/border_background" android:orientation="vertical" - android:padding="20dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> @@ -72,20 +83,19 @@ android:layout_height="wrap_content" android:text="@string/title" android:textAlignment="center" - android:layout_gravity="center_horizontal" - android:layout_marginBottom="5dp"/> + android:layout_gravity="center_horizontal" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/src/Fitbot/app/src/main/res/raw/armraise.qianim b/code/src/Fitbot/app/src/main/res/raw/armraise.qianim new file mode 100644 index 0000000..052d566 --- /dev/null +++ b/code/src/Fitbot/app/src/main/res/raw/armraise.qianim @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/src/Fitbot/app/src/main/res/raw/squat.qianim b/code/src/Fitbot/app/src/main/res/raw/squat.qianim new file mode 100644 index 0000000..a25766a --- /dev/null +++ b/code/src/Fitbot/app/src/main/res/raw/squat.qianim @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/src/Fitbot/app/src/main/res/values/styles.xml b/code/src/Fitbot/app/src/main/res/values/styles.xml index be54ae7..d2d8f81 100644 --- a/code/src/Fitbot/app/src/main/res/values/styles.xml +++ b/code/src/Fitbot/app/src/main/res/values/styles.xml @@ -25,6 +25,13 @@ 6dp + +