Removed FitnessCycle, reformatted code, made Pepper actions globally accessible

This commit is contained in:
Luca Warmenhoven
2024-05-30 17:16:09 +02:00
parent b4ce40ca7d
commit 3f37f44f22
22 changed files with 234 additions and 428 deletions

View File

@@ -19,11 +19,10 @@ public enum EMuscleGroup {
return this.muscleGroupIdentifier;
}
public static EMuscleGroup parse(String name)
{
for ( EMuscleGroup muscleGroup : EMuscleGroup.values())
for ( String muscleGroupName : muscleGroup.muscleGroupNames)
if ( muscleGroupName.equalsIgnoreCase(name))
public static EMuscleGroup parse(String name) {
for (EMuscleGroup muscleGroup : EMuscleGroup.values())
for (String muscleGroupName : muscleGroup.muscleGroupNames)
if (muscleGroupName.equalsIgnoreCase(name))
return muscleGroup;
return null;
}

View File

@@ -1,12 +1,6 @@
package com.example.fitbot.exercise;
import android.util.Log;
import com.example.fitbot.util.path.GesturePath;
import com.example.fitbot.util.server.IWebServerHandler;
import com.example.fitbot.util.server.WebServer;
import java.util.Objects;
public class Exercise {

View File

@@ -48,7 +48,6 @@ public class ExerciseManager {
* @param method The method to use for the request, e.g. GET or POST.
* @param contentType The content type of the request.
* @param body The body of the request.
*
* @return The response from the server.
*/
public static String sendHTTP(String url, String method, String contentType, String body) {
@@ -102,7 +101,7 @@ public class ExerciseManager {
// class to support more paths.
String[] leftRightData = content.get(PROPERTY_PATH).getAsString().split(";");
if ( leftRightData.length != SENSOR_COUNT)
if (leftRightData.length != SENSOR_COUNT)
return null;
return new Exercise(

View File

@@ -1,53 +0,0 @@
package com.example.fitbot.exercise;
/**
* The ExerciseUser class represents a user of the exercise application.
* This contains all necessary information of the current user.
*/
public class ExerciseUser {
public float upperArmLength;
public float lowerArmLength;
public float upperLegLength;
public float lowerLegLength;
public float height;
/**
* Constructor for the ExerciseUser class.
* @param upperArmLength The length of the upper arm.
* @param lowerArmLength The length of the lower arm.
* @param height The height of the user.
* @param upperLegLength The length of the upper leg.
* @param lowerLegLength The length of the lower leg.
*/
public ExerciseUser(float upperArmLength, float lowerArmLength, float height, float upperLegLength, float lowerLegLength) {
this.upperArmLength = upperArmLength;
this.lowerArmLength = lowerArmLength;
this.upperLegLength = upperLegLength;
this.lowerLegLength = lowerLegLength;
this.height = height;
}
/**
* Constructor for the ExerciseUser class.
* @param height The height of the user.
*/
public ExerciseUser(float height) {
this.height = height;
this.upperArmLength = height * 0.2f;
this.lowerArmLength = height * 0.2f;
this.upperLegLength = height * 0.3f;
this.lowerLegLength = height * 0.3f;
}
/**
* Default constructor for the ExerciseUser class.
* This sets the default height to 180.0f. (1.80m)
*/
public ExerciseUser() {
this(180.0f);
}
}

View File

@@ -1,15 +1,6 @@
package com.example.fitbot.pepper;
import com.aldebaran.qi.sdk.QiContext;
public abstract class AbstractPepperActionEvent {
public final QiContext context;
public AbstractPepperActionEvent(QiContext context)
{
this.context = context;
}
public abstract EPepperAction getAction();
}

View File

@@ -6,5 +6,5 @@ package com.example.fitbot.pepper;
public enum EPepperAction {
ACTION_SPEAK,
ACTION_ANIMATE,
NO_ACTION;
NO_ACTION
}

View File

@@ -1,6 +1,7 @@
package com.example.fitbot.pepper;
import com.aldebaran.qi.sdk.QiContext;
import com.aldebaran.qi.sdk.RobotLifecycleCallbacks;
import com.aldebaran.qi.sdk.object.locale.Language;
import com.aldebaran.qi.sdk.object.locale.Locale;
import com.aldebaran.qi.sdk.object.locale.Region;
@@ -11,9 +12,16 @@ import java.util.concurrent.atomic.AtomicBoolean;
/**
* Class representing the Pepper robot.
* All functionality related to Pepper is implemented in this class.
* <p>
* Before any of the functions in this class can be used, the Pepper class needs a QiContext to be
* set. This is retrieved in the `onRobotFocusGained` method of the RobotLifecycleCallbacks
* interface, and can then provided using the `provideContext` method.
* </p>
*/
public class Pepper {
// Queue containing all Pepper actions that need to be executed
// This is to prevent the app from crashing when attempting to perform multiple actions at once
private static final ConcurrentLinkedQueue<AbstractPepperActionEvent> pepperActionEventQueue =
new ConcurrentLinkedQueue<>();
@@ -23,25 +31,31 @@ public class Pepper {
private static final Locale DEFAULT_LOCALE = new Locale(Language.DUTCH, Region.NETHERLANDS);
/**
* Speak a phrase with Pepper.
* The latest QiContext used by Pepper.
* This can be publicly accessed to get the latest QiContext used by Pepper.
*/
public static QiContext latestContext = null;
/**
* Make the Pepper robot speak a phrase.
* This function adds a SpeechEvent to the event queue,
* and will speak the phrase once Pepper is ready to process it.
*
* @param phrase The phrase to speak
* @param context The QiContext to use
*/
public static void speak(String phrase, QiContext context)
{
addToEventQueue(new PepperSpeechEvent(context, phrase, DEFAULT_LOCALE));
public static void speak(String phrase) {
addToEventQueue(new PepperSpeechEvent(phrase, DEFAULT_LOCALE));
}
/**
* Animate Pepper with the given animation.
* Make the Pepper robot perform an animation.
* This function adds an AnimationEvent to the event queue,
* and will perform the animation once Pepper is ready to process it.
*
* @param animationName The name of the animation to play
* @param context The QiContext to use
*/
public static void animate(String animationName, QiContext context)
{
addToEventQueue(new PepperAnimationEvent(context, animationName));
public static void animate(String animationName) {
addToEventQueue(new PepperAnimationEvent(animationName));
}
/**
@@ -50,8 +64,7 @@ public class Pepper {
*
* @param event The event to add to the queue
*/
public static void addToEventQueue(AbstractPepperActionEvent event)
{
public static void addToEventQueue(AbstractPepperActionEvent event) {
pepperActionEventQueue.add(event);
processEventQueue();
}
@@ -63,25 +76,23 @@ public class Pepper {
* This prevents multiple actions from being executed at the same time, which can
* cause the application to crash.
*/
private static synchronized void processEventQueue()
{
if (!pepperActionEventQueue.isEmpty())
{
private static synchronized void processEventQueue() {
if (!pepperActionEventQueue.isEmpty()) {
AbstractPepperActionEvent event = pepperActionEventQueue.poll();
if (event == null)
// Prevent null pointer exceptions
if (event == null || latestContext == null)
return;
switch (event.getAction())
{
switch (event.getAction()) {
/* Event for speaking a phrase **/
case ACTION_SPEAK:
if ( !(event instanceof PepperSpeechEvent) || isSpeaking.get())
if (!(event instanceof PepperSpeechEvent) || isSpeaking.get())
break;
PepperSpeechEvent speechEvent = (PepperSpeechEvent) event;
isSpeaking.set(true);
speechEvent
.sayPhrase()
.getSay(latestContext)
.async()
.run()
.andThenConsume(future -> {
@@ -92,11 +103,11 @@ public class Pepper {
/* Event for animating the robot **/
case ACTION_ANIMATE:
if ( !(event instanceof PepperAnimationEvent) || isAnimating.get())
if (!(event instanceof PepperAnimationEvent) || isAnimating.get())
break;
PepperAnimationEvent animationEvent = (PepperAnimationEvent) event;
animationEvent
.getAnimation()
.getAnimation(latestContext)
.async()
.run()
.andThenConsume(future -> {
@@ -105,9 +116,27 @@ public class Pepper {
});
break;
default:
// Do nothing
break;
}
}
}
/**
* Function for providing the latest QiContext to Pepper.
*
* @param context The QiContext to use.
* This can be null if the context is not available.
* @param origin The origin of the context.
* This parameter is to prevent other classes from
* unnecessarily providing a context, or setting
* the context to null.
*/
public static void provideContext(QiContext context, Class<? extends RobotLifecycleCallbacks> origin) {
latestContext = context;
if (context != null) {
processEventQueue();
}
}
}

View File

@@ -9,10 +9,27 @@ import com.aldebaran.qi.sdk.object.actuation.Animation;
public class PepperAnimationEvent extends AbstractPepperActionEvent {
public final String animationName;
private final IAnimationCompletedListener onLabelReachedListener;
public PepperAnimationEvent(QiContext context, String animationName) {
super(context);
/**
* Constructor for PepperAnimationEvent
*
* @param animationName The name of the animation to play
* @param onLabelReachedListener The listener to call when the animation is completed
*/
public PepperAnimationEvent(String animationName, IAnimationCompletedListener onLabelReachedListener) {
this.animationName = animationName;
this.onLabelReachedListener = onLabelReachedListener;
}
/**
* Constructor for PepperAnimationEvent
*
* @param animationName The name of the animation to play
*/
public PepperAnimationEvent(String animationName) {
this(animationName, null);
}
@Override
@@ -24,14 +41,25 @@ public class PepperAnimationEvent extends AbstractPepperActionEvent {
* Returns an animation object, which can be used to play the animation
* in the Pepper class.
*/
public Animate getAnimation()
{
Animation animation = AnimationBuilder.with(this.context)
.withResources(this.context.getResources().getIdentifier(animationName, "raw", this.context.getPackageName()))
public Animate getAnimation(QiContext context) {
Animation animation = AnimationBuilder.with(context)
.withResources(context.getResources().getIdentifier(animationName, "raw", context.getPackageName()))
.build();
return AnimateBuilder.with(this.context)
Animate animate = AnimateBuilder.with(context)
.withAnimation(animation)
.build();
// Add a listener for when a label is reached
animate.addOnLabelReachedListener((label, time) -> {
if (onLabelReachedListener != null && "end".equals(label)) {
onLabelReachedListener.onComplete();
}
});
return animate;
}
public interface IAnimationCompletedListener {
void onComplete();
}
}

View File

@@ -10,9 +10,7 @@ public class PepperSpeechEvent extends AbstractPepperActionEvent {
public final String phrase;
public final Locale locale;
public PepperSpeechEvent(QiContext context, String phrase, Locale locale)
{
super(context);
public PepperSpeechEvent(String phrase, Locale locale) {
this.locale = locale;
this.phrase = phrase;
}
@@ -25,9 +23,8 @@ public class PepperSpeechEvent extends AbstractPepperActionEvent {
/**
* Returns a Say object, which can then be executed.
*/
public Say sayPhrase()
{
return SayBuilder.with(this.context)
public Say getSay(QiContext context) {
return SayBuilder.with(context)
.withText(this.phrase)
.withLocale(this.locale)
.build();

View File

@@ -1,6 +1,9 @@
package com.example.fitbot.ui.activities;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.widget.VideoView;
import com.aldebaran.qi.sdk.QiContext;
@@ -12,9 +15,8 @@ import com.example.fitbot.R;
import com.example.fitbot.exercise.Exercise;
import com.example.fitbot.exercise.ExerciseManager;
import com.example.fitbot.pepper.Pepper;
import com.example.fitbot.ui.components.PersonalMotionPreviewElement;
import com.example.fitbot.ui.components.ExerciseStatusElement;
import com.example.fitbot.util.NavigationManager;
import com.example.fitbot.util.FitnessCycle;
import com.example.fitbot.util.processing.InputProcessor;
import org.joml.Vector3f;
@@ -22,14 +24,12 @@ import org.joml.Vector3f;
public class FitnessActivity extends RobotActivity implements RobotLifecycleCallbacks {
// Private fields for the FitnessActivity class.
private PersonalMotionPreviewElement personalMotionPreviewElement;
private ExerciseStatusElement 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[] {
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.",
", de oefeningen slapen nog, probeer het later nog eens."
@@ -54,17 +54,16 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
// Find the VideoView by its ID
VideoView videoView = findViewById(R.id.videoView);
FitnessCycle.playVideo(videoView, this);
playVideo(videoView, this);
NavigationManager.setupButtonNavigation(this, R.id.homeButtonFitness, MainActivity.class);
NavigationManager.hideSystemUI(this);
}
@Override
public void onRobotFocusGained(QiContext qiContext) {
this.qiContext = qiContext;
// Find the VideoView by its ID
// CompletableFuture.runAsync(() -> FitnessCycle.executeMovement("bicepcurl", 10, qiContext));
// Provide the context so that all queued actions can be performed.
Pepper.provideContext(qiContext, this.getClass());
personalMotionPreviewElement = findViewById(R.id.personalMotionPreviewElement);
@@ -74,19 +73,18 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
personalMotionPreviewElement.post(() -> {
this.acquireExercise((exercise) -> {
// Acquire paths from the exercise and provide them to the motion processor
Vector3f[][] vectors = new Vector3f[][] {exercise.leftPath.getVectors(), exercise.rightPath.getVectors()};
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);
}, (n) -> {
int randomMessageIndex = (int)Math.floor(Math.random() * EXERCISE_NOT_FOUND_MESSAGES.length);
Pepper.speak(EXERCISE_NOT_FOUND_MESSAGES[randomMessageIndex], qiContext);
Pepper.speak(EXERCISE_NOT_FOUND_SEEK_HELP_MESSAGE, qiContext);
int randomMessageIndex = (int) Math.floor(Math.random() * EXERCISE_NOT_FOUND_MESSAGES.length);
Pepper.speak(EXERCISE_NOT_FOUND_MESSAGES[randomMessageIndex]);
Pepper.speak(EXERCISE_NOT_FOUND_SEEK_HELP_MESSAGE);
NavigationManager.navigateToActivity(this, MainActivity.class);
});
});
@@ -104,33 +102,59 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
// therefore we'll have to do it like this...
(new Thread(() -> {
Exercise exercise = ExerciseManager.retrieveExercise();
if ( exercise == null)
{
if (exercise == null) {
onFailedFetch.handle(null);
}
else {
} else {
onSuccessfulFetch.handle(exercise);
}
})).start();
}
/**
* Function for playing a video in a VideoView
*
* @param videoView The VideoView to play the video in
* @param context The context to use
*/
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.start();
} else {
Log.e("FitnessActivity", "VideoView is null. Check your layout XML.");
}
}
@Override
public void onRobotFocusLost() {
QiSDK.unregister(this, this);
Pepper.provideContext(null, this.getClass()); // Remove the context (unavailable)
// Go to the main screen
NavigationManager.navigateToActivity(this, MainActivity.class);
}
@Override
public void onRobotFocusRefused(String reason) {}
public void onRobotFocusRefused(String reason) {
}
@Override
protected void onDestroy() {
if ( this.motionProcessor != null ) {
if (this.motionProcessor != null) {
this.motionProcessor.stopListening();
this.motionProcessor = null;
}
QiSDK.unregister(this, this);
Pepper.provideContext(null, this.getClass());
super.onDestroy();
}
}

View File

@@ -1,9 +1,7 @@
package com.example.fitbot.ui.activities;
import android.app.Activity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.support.v7.app.AppCompatActivity;
import com.example.fitbot.R;
import com.example.fitbot.util.NavigationManager;

View File

@@ -9,13 +9,12 @@ 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.pepper.Pepper;
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.NavigationManager;
import com.example.fitbot.util.FitnessCycle;
import com.example.fitbot.util.processing.IInputHandler;
import com.example.fitbot.util.processing.InputProcessor;
@@ -26,12 +25,11 @@ import org.joml.Vector4f;
import java.util.concurrent.ConcurrentLinkedQueue;
public class PersonalMotionPreviewElement extends View implements IInputHandler {
public class ExerciseStatusElement extends View implements IInputHandler {
// Fields regarding Exercise and speech handling.
private InputProcessor motionProcessor;
private Exercise exercise;
private QiContext qiContext;
private int exerciseCount;
private FitnessActivity parentActivity;
@@ -41,10 +39,10 @@ public class PersonalMotionPreviewElement extends View implements IInputHandler
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 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 ConcurrentLinkedQueue<Vector2f> vectors = new ConcurrentLinkedQueue<>();
private final ConcurrentLinkedQueue<Vector2f> vectors = new ConcurrentLinkedQueue<>();
// TODO: Remove
private Vector2f[] axisVectors = new Vector2f[0];
@@ -57,11 +55,10 @@ public class PersonalMotionPreviewElement extends View implements IInputHandler
};
public PersonalMotionPreviewElement(Context context, AttributeSet attrs) {
public ExerciseStatusElement(Context context, AttributeSet attrs) {
super(context, attrs);
if ( context instanceof Activity)
{
if (context instanceof Activity) {
this.parentActivity = (FitnessActivity) context;
}
@@ -96,8 +93,8 @@ public class PersonalMotionPreviewElement extends View implements IInputHandler
this.exercise = exercise;
this.exerciseCount = exerciseCount;
// TODO: Remove
this.axisVectors = new Vector2f[] {
/* 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()),
@@ -106,25 +103,24 @@ public class PersonalMotionPreviewElement extends View implements IInputHandler
projectVertex(new Vector3f(0, 0, 5.0f), getWidth(), getHeight())
};
Pepper.speak(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 (this.motionProcessor.hasFinished()) {
// If for some reason the parent activity is not defined,
// move back to the main screen.
if ( this.parentActivity == null)
{
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 )
{
if (this.exerciseCount > 0) {
this.exerciseCount--;
this.parentActivity.acquireExercise((newExercise) -> {
this.motionProcessor.useExercise(newExercise);
@@ -132,43 +128,27 @@ public class PersonalMotionPreviewElement extends View implements IInputHandler
// Move to main screen
NavigationManager.navigateToActivity(parentActivity, MainActivity.class);
});
}
else
{
} else {
// Finish the exercise.
NavigationManager.navigateToActivity(parentActivity, EndScreenActivity.class);
return;
}
}
// TODO: Adjust / remove
/* TODO: Use raw vector */
vectors.add(projectVertex(rotationVector, this.getWidth(), this.getHeight()));
Log.i("MotionProcessor", "Rotation vector received: " + rotationVector);
/* TODO: Remove */
Vector2f parsed = projectVertex(rotationVector, this.getWidth(), this.getHeight());
this.vectors.add(parsed);
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();
});
}
/**
* Function for providing a QiContext to the PersonalMotionPreviewElement.
* This function will be called by the parent activity when the QiContext is available.
* Also say something nice to the user :)
*
* @param context The QiContext to provide.
*/
public void provideQiContext(QiContext context) {
this.qiContext = context;
if (this.qiContext == null)
return;
FitnessCycle.say(STARTING_PHRASES[(int) Math.floor(Math.random() * STARTING_PHRASES.length)], this.qiContext);
}
/**
* Method for setting the gesture path that will be drawn on the canvas.
*
@@ -203,7 +183,6 @@ public class PersonalMotionPreviewElement extends View implements IInputHandler
}
@Override
public void onDraw(Canvas canvas) {
canvas.drawRect(0, 0, getWidth(), getHeight(), backgroundPaint);
@@ -211,17 +190,17 @@ public class PersonalMotionPreviewElement extends View implements IInputHandler
/*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.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));
/* 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)
{
for (Vector2f point : this.vectors) {
canvas.drawRect(point.x, point.y, point.x + 5, point.y + 5, this.userProgressPaint);
}
/*

View File

@@ -11,8 +11,7 @@ import com.aldebaran.qi.sdk.object.actuation.Animation;
public class Animations {
public static void Animate(String AnimationFile, QiContext ctx)
{
public static void Animate(String AnimationFile, QiContext ctx) {
int resId = ctx.getResources().getIdentifier(AnimationFile, "raw", ctx.getPackageName());
Animation animation = AnimationBuilder.with(ctx)

View File

@@ -1,164 +0,0 @@
package com.example.fitbot.util;
import android.content.Context;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.VideoView;
import com.aldebaran.qi.sdk.builder.SayBuilder;
import com.aldebaran.qi.sdk.object.conversation.Phrase;
import com.aldebaran.qi.sdk.object.conversation.Say;
import com.aldebaran.qi.sdk.object.locale.Language;
import com.aldebaran.qi.sdk.object.locale.Locale;
import com.aldebaran.qi.sdk.object.locale.Region;
import com.example.fitbot.R;
import com.aldebaran.qi.sdk.QiContext;
import com.aldebaran.qi.sdk.builder.AnimateBuilder;
import com.aldebaran.qi.sdk.builder.AnimationBuilder;
import com.aldebaran.qi.sdk.object.actuation.Animate;
import com.aldebaran.qi.sdk.object.actuation.Animation;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
public class FitnessCycle extends AppCompatActivity {
private static final Locale DUTCH_LOCALE = new Locale(Language.DUTCH, Region.NETHERLANDS);
private static final BlockingQueue<PepperSpeechEntry> MESSAGE_QUEUE = new LinkedBlockingQueue<>();
private static boolean SPEECH_ACTIVE = false;
/**
* Function for executing a movement animation a certain number of times
* on the robot
*
* @param Exercise The name of the exercise to perform
* @param Reps The number of repetitions to perform
* @param qiContext The QiContext to use
*/
public static void executeMovement(String Exercise, int Reps, QiContext qiContext) {
AtomicInteger repCount = new AtomicInteger(0);
Animation animation = AnimationBuilder.with(qiContext)
.withResources(qiContext.getResources().getIdentifier(Exercise, "raw", qiContext.getPackageName()))
.build();
Animate animate = AnimateBuilder.with(qiContext)
.withAnimation(animation)
.build();
// Add a listener for when a label is reached
animate.addOnLabelReachedListener((label, time) -> {
// Increment repCount when the end of a repetition is reached
if ("end".equals(label)) {
repCount.incrementAndGet();
}
});
// Run the animation the desired number of times
for (int i = 0; i < Reps; i++) {
animate.run();
}
}
/**
* Function for making the robot say something with DUTCH_LOCALE as locale
*
* @param phrase The phrase to make the robot say
* @param ctx The QiContext to use
*/
public static void say(String phrase, QiContext ctx) {
say(phrase, ctx, DUTCH_LOCALE);
}
/**
* Function for making the robot say something with a specific locale
*
* @param phrase The phrase to make the robot say
* @param ctx The QiContext to use
* @param locale The locale to use
*/
public static void say(String phrase, QiContext ctx, Locale locale) {
// If the robot is already speaking, add the phrase to the queue
MESSAGE_QUEUE.add(new PepperSpeechEntry(phrase, ctx, locale));
processMessageQueue();
}
private static synchronized void processMessageQueue() {
if (SPEECH_ACTIVE)
return;
PepperSpeechEntry nextPhrase = MESSAGE_QUEUE.poll();
if (nextPhrase != null) {
SPEECH_ACTIVE = true;
Say say = nextPhrase.getSay();
say.async().run().andThenConsume(future -> {
SPEECH_ACTIVE = false;
processMessageQueue(); // Process next item in the queue
});
}
}
/**
* Function for playing a video in a VideoView
*
* @param videoView The VideoView to play the video in
* @param context The context to use
*/
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.start();
} else {
Log.e("FitnessActivity", "VideoView is null. Check your layout XML.");
}
}
private static class PepperAnimateEvent extends PepperActionEvent {
}
private static class PepperSpeechEntry extends PepperActionEvent {
public String phrase;
public QiContext context;
public Locale locale;
public PepperSpeechEntry(String phrase, QiContext context, Locale locale) {
this.action = PepperAction.SPEAK;
this.data = this;
this.phrase = phrase;
this.context = context;
this.locale = locale;
}
public Say getSay() {
return SayBuilder
.with(context)
.withLocale(locale)
.withText(phrase)
.build();
}
}
private enum PepperAction {
SPEAK,
ANIMATE,
MOVE
}
private static class PepperActionEvent {
public PepperAction action;
public Object data;
}
}

View File

@@ -6,10 +6,6 @@ import android.content.Intent;
import android.view.View;
import android.widget.Button;
import com.example.fitbot.ui.activities.FitnessActivity;
import com.example.fitbot.ui.activities.MainActivity;
import com.example.fitbot.ui.activities.HelpActivity;
public class NavigationManager {
/**
@@ -21,8 +17,7 @@ public class NavigationManager {
*/
public static void setupButtonNavigation(Activity currentActivity, int buttonId, Class<? extends Activity> targetActivity) {
Button button = currentActivity.findViewById(buttonId);
if ( button == null )
{
if (button == null) {
throw new IllegalArgumentException("Button with ID " + buttonId + " not found in " + currentActivity.getClass().getSimpleName());
}
button.setOnClickListener(v -> NavigationManager.navigateToActivity(currentActivity, targetActivity));

View File

@@ -2,10 +2,6 @@ package com.example.fitbot.util.path;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class GesturePath {
// The vectors that make up the path.
@@ -16,18 +12,18 @@ public class GesturePath {
*
* @param vectors The vectors that make up the path.
*/
public GesturePath(Vector3f[] vectors)
{
if ( vectors.length < 2)
public GesturePath(Vector3f[] vectors) {
if (vectors.length < 2)
throw new IllegalArgumentException("A path must have at least two points.");
this.segments = new PathSegment[vectors.length - 1];
for ( int i = 0; i < vectors.length - 1; i++)
for (int i = 0; i < vectors.length - 1; i++)
segments[i] = new PathSegment(vectors[i], vectors[i + 1]);
}
/**
* Constructor for a GesturePath with provided PathSegments.
*
* @param segments The PathSegments to initialize the path with.
*/
public GesturePath(PathSegment... segments) {
@@ -46,11 +42,10 @@ public class GesturePath {
/**
* Method for retrieving the vectors of the GesturePath.
*/
public Vector3f[] getVectors()
{
public Vector3f[] getVectors() {
Vector3f[] vectors = new Vector3f[segments.length + 1];
vectors[0] = segments[0].getStart();
for ( int i = 0; i < segments.length; i++)
for (int i = 0; i < segments.length; i++)
vectors[i + 1] = segments[i].getEnd();
return vectors;
@@ -64,11 +59,11 @@ public class GesturePath {
*/
public PathSegment closest(Vector3f reference) {
// If there's only one segment, return that one.
if ( segments.length == 1)
if (segments.length == 1)
return segments[0];
PathSegment closest = segments[0];
for ( int i = 1; i < segments.length; i++)
for (int i = 1; i < segments.length; i++)
closest = PathSegment.closer(closest, segments[i], reference);
return closest;
@@ -89,7 +84,7 @@ public class GesturePath {
* 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.
*
* <p>
* Note: ASCII to Vector conversion is done in Big Endian format (most significant byte first).
*
* @param input The string to convert

View File

@@ -84,7 +84,7 @@ public class PathSegment {
* @return The distance to the closest point on the path segment.
*/
public double distance(Vector3f reference) {
if ( this.start.distanceSquared(reference) > this.end.distanceSquared(reference))
if (this.start.distanceSquared(reference) > this.end.distanceSquared(reference))
return this.end.distance(reference);
return this.start.distance(reference);
}

View File

@@ -6,6 +6,7 @@ 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.
*/

View File

@@ -49,6 +49,7 @@ public class InputProcessor {
* 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) {
@@ -61,6 +62,7 @@ public class InputProcessor {
/**
* Function for checking if the exercise has finished.
*
* @return True if the exercise has finished, false otherwise.
*/
public boolean hasFinished() {
@@ -171,8 +173,7 @@ public class InputProcessor {
*
* @return The current progress of the exercise.
*/
public double getCurrentProgress()
{
public double getCurrentProgress() {
return secondsPassed / exerciseDuration;
}
@@ -208,8 +209,7 @@ public class InputProcessor {
// 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)
{
selfIndex >= 0 && selfIndex <= this.selfRotationVectorPaths[sensorId].length - 1) {
return this.selfRotationVectorPaths[sensorId][selfIndex].distance(this.targetRotationVectorPaths[sensorId][targetIndex]);
}
return 0.0d;

View File

@@ -1,7 +1,5 @@
package com.example.fitbot.util.server;
import java.net.Socket;
/**
* Interface for handling WebSocket events.
*/

View File

@@ -10,16 +10,13 @@ import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
public class WebServer implements Runnable {
private ServerSocket serverSocket;
protected IWebServerHandler eventHandler = (input) -> {}; // No-op.
protected IWebServerHandler eventHandler = (input) -> {
}; // No-op.
private Thread thread;
private final AtomicBoolean forceClose = new AtomicBoolean(false);
@@ -84,7 +81,7 @@ public class WebServer implements Runnable {
String[] data = builder.toString().split("\n\n");
if ( data.length > 1) { // Check if the data is valid.
if (data.length > 1) { // Check if the data is valid.
this.eventHandler.onReceive(data[1]);
}

View File

@@ -56,7 +56,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.example.fitbot.ui.components.PersonalMotionPreviewElement
<com.example.fitbot.ui.components.ExerciseStatusElement
android:id="@+id/personalMotionPreviewElement"
android:layout_width="350dp"
android:layout_height="350dp" />