Merge branch 'main' of ssh://gitlab.fdmci.hva.nl/propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-4/muupooviixee66

This commit is contained in:
Niels Gras
2024-06-07 14:28:40 +02:00
12 changed files with 121 additions and 121 deletions

View File

@@ -23,6 +23,7 @@ void Connectivity::connectWiFi(char* ssid, char* pass){
const char* getServerURL = "http://145.92.8.132:443/get-ip";
String ipAddress = ""; // string that will hold the server's IP address
/** Fetch the IP address of pepper from the server */
const char* Connectivity::fetchIPAddress() {
char* ipAddress = NULL; // Declare ipAddress as a char*
if (WiFi.status() == WL_CONNECTED) {
@@ -41,14 +42,14 @@ const char* Connectivity::fetchIPAddress() {
ipAddress = strdup(ip);
}
} else {
Serial.printf("GET request failed, error: %s\n", http.errorToString(httpCode).c_str());
Serial.printf("GET request failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
} else {
Serial.println("WiFi not connected");
}
return ipAddress; // Add this return statement
return ipAddress;
}
/** Send a POST request to a server with provided data */

View File

@@ -14,8 +14,8 @@
#include <ArduinoJson.h>
class Connectivity {
// declare the class Connectivity with all functions
class Connectivity {
public:
void connectWiFi(char* ssid, char* pass);
void websocketSetup(char* ip, uint16_t port, char* adress);

View File

@@ -4,11 +4,10 @@ void setup() {
//connect to internet and start sensor
connectivity.connectWiFi(ssid, pass);
sensorManager.sensorSetup();
Serial.begin(115200);
Serial.println("startup");
}
void loop() {
//get data from sensor
SensorManager::eulerAngles Rotation = sensorManager.getEulerAngles();
//static structure
@@ -28,6 +27,7 @@ struct acceleration {
unsigned long currentTime = millis();
if (currentTime - lastTime >= 100) { // do everything inside every 100 ms
memset(buffer, 0, BUFFER_SIZE);
//convert string to char*
sprintf(
buffer,
"{\"deviceId\": %d, \"rotationX\": %f, \"rotationY\": %f, \"rotationZ\": %f, \"accelerationX\": %f, \"accelerationY\": %f, \"accelerationZ\": %f, \"type\": %s}",
@@ -40,9 +40,8 @@ struct acceleration {
accelData.z,
"data");
// %d = int, %f = floatation, %s = string
//send data to pepper
connectivity.httpPost(serverIp, "/", 3445, buffer, strlen(buffer), "application/json");
Serial.println(serverIp);
Serial.println(buffer);
lastTime = currentTime;
}
}

View File

@@ -37,7 +37,7 @@ public class ExerciseManager {
};
public static final int DEFAULT_EXERCISE_REPETITIONS = 10;
public static final float EXERCISE_ERROR_MARGIN = 1.0f;
public static final float EXERCISE_ERROR_MARGIN = 1.5f;
public static final float EXERCISE_TIME_SCALING_FACTOR = 1.0f;
// Fields representing the statistics of the user

View File

@@ -1,4 +0,0 @@
package com.example.fitbot.ui.activities;
public class CompletionActivity {
}

View File

@@ -16,6 +16,5 @@ public class EndScreenActivity extends AppCompatActivity {
NavigationManager.hideSystemUI(this);
NavigationManager.setupButtonNavigation(this, R.id.homeButtonEndScreen, MainActivity.class);
NavigationManager.setupButtonNavigation(this, R.id.startButtonEndScreen, FitnessActivity.class);
}
}

View File

@@ -10,6 +10,7 @@ import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.util.Log;
@@ -19,16 +20,12 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.VideoView;
import com.aldebaran.qi.Future;
import com.aldebaran.qi.sdk.QiContext;
import com.aldebaran.qi.sdk.QiSDK;
import com.aldebaran.qi.sdk.RobotLifecycleCallbacks;
import com.aldebaran.qi.sdk.builder.AnimateBuilder;
import com.aldebaran.qi.sdk.builder.AnimationBuilder;
import com.aldebaran.qi.sdk.design.activity.RobotActivity;
import com.aldebaran.qi.sdk.design.activity.conversationstatus.SpeechBarDisplayStrategy;
import com.aldebaran.qi.sdk.object.actuation.Animate;
import com.aldebaran.qi.sdk.object.actuation.Animation;
import com.example.fitbot.R;
import com.example.fitbot.exercise.Exercise;
import com.example.fitbot.exercise.ExerciseManager;
@@ -45,7 +42,7 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
// Progress circle for the exercises
private ProgressBar progressCircle;
private TextView progressText;
private int progress = 0;
private int progress = 1;
private final int maxProgress = 10;
// Exercise status element data
@@ -72,7 +69,7 @@ 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 = 1;
private static int EXERCISE_VIDEO_REPETITION = 1;
private static final float EXERCISE_SPEED_MULTIPLIER = 1.0f;
@Override
@@ -88,7 +85,7 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
//this.exerciseDescriptionTextView = findViewById(R.id.textViewDialogDescription);
// Set the repetition count for the exercise
EXERCISE_REP = 1;
EXERCISE_VIDEO_REPETITION = 1;
progressCircle = findViewById(R.id.progressCircle);
progressText = findViewById(R.id.progressText);
@@ -179,6 +176,7 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
// Start checking for user movement once the video has loaded
this.motionProcessor.startListening();
this.motionProcessor.startCheckingUserMovement();
// this.motionProcessor.setRecording(true, 4.f);
return true;
}
@@ -192,9 +190,9 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
}
videoView.setOnCompletionListener(mp -> {
if (EXERCISE_REP < EXERCISE_COUNT) {
if (EXERCISE_VIDEO_REPETITION < ExerciseManager.DEFAULT_EXERCISE_REPETITIONS) {
videoView.start(); // start the video again
EXERCISE_REP++;
EXERCISE_VIDEO_REPETITION++;
}
});
});
@@ -211,7 +209,7 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
public void playVideo(VideoView videoView, Context context) {
// Set up the video player
if (videoView != null) {
videoView.setVideoPath(exerciseVideoUrl);
videoView.setVideoURI(Uri.parse("android.resource://" + context.getPackageName() + "/" + R.raw.arm_raises));
videoView.start();
} else {
Log.e("FitnessActivity", "VideoView is null. Check your layout XML.");
@@ -267,8 +265,7 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
}
public void incrementProgress() {
if (progress < maxProgress) {
progress++;
if (progress++ < maxProgress) {
triggerColorBurst(true);
updateProgress();
}
@@ -281,19 +278,25 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
public void triggerColorBurst(boolean isGoodRep) {
int circleId = isGoodRep ? R.drawable.progress_circle_good : R.drawable.progress_circle_bad;
int soundId = isGoodRep ? R.raw.good_sound : R.raw.wrong_sound;
progressCircle.setProgressDrawable(ContextCompat.getDrawable(this, circleId));
MediaPlayer.create(this, soundId).start();
Log.i("InputProcessor", "Color Burst");
ObjectAnimator animator = ObjectAnimator.ofFloat(progressCircle, "alpha", 1f, 0f, 1f);
animator.setDuration(700); // Burst duration
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
progressCircle.setProgressDrawable(ContextCompat.getDrawable(FitnessActivity.this, R.drawable.progress_circle));
}
});
animator.start();
try {
int circleId = isGoodRep ? R.drawable.progress_circle_good : R.drawable.progress_circle_bad;
int soundId = isGoodRep ? R.raw.good_sound : R.raw.wrong_sound;
progressCircle.setProgressDrawable(ContextCompat.getDrawable(this, circleId));
// MediaPlayer.create(this, soundId).start();
ObjectAnimator animator = ObjectAnimator.ofFloat(progressCircle, "alpha", 1f, 0f, 1f);
animator.setDuration(700); // Burst duration
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
progressCircle.setProgressDrawable(ContextCompat.getDrawable(FitnessActivity.this, R.drawable.progress_circle));
}
});
animator.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -17,17 +17,18 @@ import com.google.gson.JsonParser;
import org.jetbrains.annotations.NotNull;
import org.joml.Vector3f;
import org.json.JSONArray;
import java.util.ArrayList;
import java.util.List;
public class InputProcessor {
private List<Vector3f>[] selfRotationVectorPaths; // Relative path of the motion data
private List<Vector3f>[] selfRotationVectorPaths = null; // Relative path of the motion data
private Vector3f[][] targetRotationVectorPaths; // Target path of the motion data
private float exerciseRepetitionDurationInSeconds = 0.0f;
private int exercisesRemaining = 0;
private int exercisesRemaining = 1;
private float errorCheckInterval_s;
private int checksPerformed = 0;
@@ -104,9 +105,20 @@ public class InputProcessor {
public void startCheckingUserMovement() {
// Error checking thread.
(new Thread(() -> {
Log.i("InputProcessor", "Movement Checking Thread started");
while (this.exercisesRemaining > 0) {
if ( this.totalChecks == 0 || this.selfRotationVectorPaths == null
|| this.selfRotationVectorPaths.length == 0
|| this.selfRotationVectorPaths[0].size() == 0
|| this.selfRotationVectorPaths[1].size() == 0)
continue;
boolean isFaulty = this.isFaultyMovement();
Log.i("InputProcessor", "Movement checked: " + (isFaulty ? "Faulty" : "Good"));
if (isFaulty) {
this.onInadequateRepetition();
} else this.onAdequateRepetition();
@@ -114,7 +126,11 @@ public class InputProcessor {
this.checksPerformed++;
if (this.checksPerformed >= this.totalChecks)
{
this.checksPerformed = 0;
this.exercisesRemaining--;
acquireExercise();
}
try {
Thread.sleep((long) (this.errorCheckInterval_s * 1000));
@@ -131,21 +147,21 @@ public class InputProcessor {
* @param exercise The exercise to move on to.
*/
private void nextExercise(Exercise exercise) {
if (this.exercisesRemaining-- <= 0) {
//TODO: Remove when more than one exercise
if (this.exercisesRemaining <= 0) {
Log.i("InputProcessor", "Moving to end screen; finished all exercises");
// Move to end screen on main activity
this.parentActivity.runOnUiThread(() -> NavigationManager.navigateToActivity(this.parentActivity, EndScreenActivity.class));
return;
}
Log.i("InputProcessor", "Acquired next exercise: " + exercise.name);
ExerciseManager.TOTAL_REPETITIONS_REQUIRED += ExerciseManager.DEFAULT_EXERCISE_REPETITIONS;
ExerciseManager.TOTAL_EXERCISES_PREFORMED++;
this.checksPerformed = 0;
this.totalChecks = ExerciseManager.DEFAULT_EXERCISE_REPETITIONS * 6;
this.errorCheckInterval_s = this.exerciseRepetitionDurationInSeconds * ExerciseManager.DEFAULT_EXERCISE_REPETITIONS / 6.0f;
this.selfRotationVectorPaths = new ArrayList[2];
this.selfRotationVectorPaths[0] = new ArrayList<>();
this.selfRotationVectorPaths[1] = new ArrayList<>();
@@ -156,6 +172,11 @@ public class InputProcessor {
this.exerciseRepetitionDurationInSeconds = exercise.exerciseTimeInSeconds;
this.secondsPassed = 0.0D;
this.lastTime = System.currentTimeMillis();
Log.i("InputProcessor", "Repetition time: " + exercise.exerciseTimeInSeconds);
this.exerciseRepetitionDurationInSeconds = Math.max(this.exerciseRepetitionDurationInSeconds, 2);
this.errorCheckInterval_s = exercise.exerciseTimeInSeconds / 3.0f;
Log.i("InputProcessor", "Exercise error checking interval: " + this.errorCheckInterval_s);
}
/**
@@ -163,14 +184,16 @@ public class InputProcessor {
*/
public void onAdequateRepetition() {
ExerciseManager.TOTAL_REPETITIONS_PERFORMED++;
this.parentActivity.incrementProgress();
Log.i("InputProcessor", "Adequate repetition performed");
this.parentActivity.runOnUiThread(this.parentActivity::incrementProgress);
}
/**
* Method that is called whenever the user performs a bad repetition.
*/
public void onInadequateRepetition() {
this.parentActivity.triggerColorBurst(false);
Log.i("InputProcessor", "Inadequate repetition performed");
this.parentActivity.runOnUiThread(() -> this.parentActivity.triggerColorBurst(false));
}
/**
@@ -231,7 +254,17 @@ public class InputProcessor {
* Upon successful retrieval, it will call the nextExercise method.
*/
private void acquireExercise() {
this.exercisesRemaining--;
if ( this.exercisesRemaining <= 0)
{
Log.i("InputProcessor", "Exercises finished");
this.parentActivity.runOnUiThread(() -> {
NavigationManager.navigateToActivity(this.parentActivity, EndScreenActivity.class);
});
return;
}
Log.i("MotionProcessor", "Fetching exercise data.");
this.parentActivity.fetchExerciseAsync(this::nextExercise, (nil) -> {
@@ -386,29 +419,31 @@ public class InputProcessor {
* Function for checking whether the last movement was faulty
*/
public boolean isFaultyMovement() {
// Calculate the index of the reference rotation vector
// This is done by calculating the closest index of the last received vector
// to the current time.
// i = | (t % T) / T * N |
int i, referenceIndex;
float distance;
for (i = 0; i < selfRotationVectorPaths.length; i++) {
referenceIndex = (int) (Math.round(
((this.secondsPassed % this.exerciseRepetitionDurationInSeconds) /
(this.exerciseRepetitionDurationInSeconds)) * this.targetRotationVectorPaths[i].length))
% this.targetRotationVectorPaths[i].length;
// If distance is greater than the threshold, return true
distance = this.selfRotationVectorPaths[i].get(this.selfRotationVectorPaths[i].size() - 1).distance(
this.targetRotationVectorPaths[i][referenceIndex]);
if (distance > ExerciseManager.EXERCISE_ERROR_MARGIN) {
Log.i("MotionProcessor", "Faulty movement detected: " + distance + " > " + ExerciseManager.EXERCISE_ERROR_MARGIN);
return true;
boolean upMovementDetected = false;
boolean downMovementDetected = false;
for (List<Vector3f> path : selfRotationVectorPaths) {
if (path.size() < 2) {
continue; // Skip if there are not enough points to compare
}
Vector3f firstPoint = path.get(0);
Vector3f lastPoint = path.get(path.size() - 1);
float y1 = firstPoint.y;
float y2 = lastPoint.y;
if (y2 > y1) {
upMovementDetected = true;
} else if (y2 < y1) {
downMovementDetected = true;
}
if (upMovementDetected && downMovementDetected) {
return false; // Return false for faulty movement if both up and down movements are detected
}
}
return false;
return true; // Return true for faulty movement if only up or down movement is detected
}
}

View File

@@ -8,13 +8,14 @@
tools:context=".ui.activities.HelpActivity">
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="800dp"
android:layout_height="450dp"
android:layout_height="wrap_content"
android:layout_marginStart="80dp"
android:layout_marginTop="24dp"
android:layout_marginTop="80dp"
android:background="@drawable/border_background"
android:orientation="vertical"
android:padding="16dp"
android:padding="40dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
@@ -26,8 +27,8 @@
android:layout_marginBottom="40dp"
android:background="@drawable/border_background_3"
android:orientation="vertical"
android:paddingVertical="15dp"
android:paddingHorizontal="20dp"
android:paddingVertical="15dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
@@ -56,27 +57,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/voltooid"
android:textAlignment="center"/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:background="@drawable/border_background_3"
android:orientation="vertical"
android:padding="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/textViewScoreEndScreen"
style="@style/TextStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/score"
android:textAlignment="center"/>
android:textAlignment="center" />
</LinearLayout>
</LinearLayout>
@@ -85,8 +66,8 @@
android:id="@+id/homeButtonEndScreen"
android:layout_width="150dp"
android:layout_height="75dp"
android:layout_marginEnd="280dp"
android:layout_marginBottom="30dp"
android:layout_marginEnd="404dp"
android:layout_marginBottom="48dp"
android:background="@drawable/red_button_gradient"
android:drawableTop="@drawable/ic_baseline_home_48"
android:drawableTint="@color/white"
@@ -94,20 +75,4 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/startButtonEndScreen"
android:layout_width="150dp"
android:layout_height="75dp"
android:layout_marginStart="280dp"
android:layout_marginBottom="30dp"
android:background="@drawable/red_button_gradient"
android:padding="15dp"
android:text="@string/start"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="30sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</android.support.constraint.ConstraintLayout>

View File

@@ -113,7 +113,7 @@
android:layout_gravity="center"
android:indeterminate="false"
android:max="10"
android:progress="0"
android:progress="1"
android:progressDrawable="@drawable/progress_circle" />
<TextView
@@ -122,7 +122,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="0/10" />
android:text="1/10" />
</RelativeLayout>

Binary file not shown.

View File

@@ -2,12 +2,14 @@
### Embedded hardware
- [BNO085](https://shop.slimevr.dev/products/slimevr-imu-module-bno085) (IMU)
- [Custom PCB](https://github.com/Sorakage033/SlimeVR-CheeseCake/tree/main/002-%E2%80%98%E2%80%99Choco%E2%80%98%E2%80%99SpecialRemake) (Use file 8 9 and 10 and pcb thickness 1 mm)
- [Battery](https://nl.aliexpress.com/item/32583443309.html) (900 mAh)
- [3D print model](https://github.com/Sorakage033/SlimeVR-CheeseCake/blob/main/004-3D%20Print%20Model/001.3-Chocolate-Case_TypeC-Only.stl)
- [Optional Acrylic lid](https://github.com/Sorakage033/SlimeVR-CheeseCake/blob/main/004-3D%20Print%20Model/acryliclid.svg)
| Part | Amount needed for 1 tracker | Price in Euros | Extra notes | |
|---|---|---|---|---|
| - [BNO085](https://shop.slimevr.dev/products/slimevr-imu-module-bno085) (IMU) | 1 | 12 | | |
| - [Custom PCB](https://github.com/Sorakage033/SlimeVR-CheeseCake/tree/main/002-%E2%80%98%E2%80%99Choco%E2%80%98%E2%80%99SpecialRemake) | 1 | ~5 at 30 pieces | (Use file 8, 9 and 10 and use pcb thickness 1mm | |
| - [Battery](https://nl.aliexpress.com/item/32583443309.html) (900 mAh) | 1 | ~4 at 20 pieces | | |
| - [3D print model](https://github.com/Sorakage033/SlimeVR-CheeseCake/blob/main/004-3D%20Print%20Model/001.3-Chocolate-Case_TypeC-Only.stl) | 1 | 0,2 | | |
| - [Optional Acrylic lid](https://github.com/Sorakage033/SlimeVR-CheeseCake/blob/main/004-3D%20Print%20Model/acryliclid.svg) | 1 | 0,50 | | |
| | | | | |
#### Extra notes for assembly
* Watch out when inserting the assembled product into the case. Make sure you put it in at an angle so you don't break the power switch.