Updated exercise handling code to make pepper more verbal
This commit is contained in:
@@ -40,4 +40,8 @@ public class Exercise {
|
||||
this.videoUrl = videoUrl;
|
||||
this.exerciseTimeInSeconds = exerciseTimeInSeconds;
|
||||
}
|
||||
|
||||
public interface ExerciseFetchHandler {
|
||||
void handle(Exercise exercise);
|
||||
}
|
||||
}
|
||||
|
@@ -18,23 +18,28 @@ public class ExerciseManager {
|
||||
// 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_EXERCISE_DURATION = "duration";
|
||||
private static final String PROPERTY_EXERCISE_ID = "exerciseId";
|
||||
private static final String PROPERTY_MUSCLE_GROUP = "muscleGroup";
|
||||
private static final String PROPERTY_DESC = "description";
|
||||
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_PATH = "path";
|
||||
private static final String PROPERTY_NAME = "name";
|
||||
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
|
||||
PROPERTY_VIDEO_URL, PROPERTY_NAME, PROPERTY_PATH,
|
||||
PROPERTY_EXERCISE_DURATION, PROPERTY_EXERCISE_ID,
|
||||
PROPERTY_SHORT_DESC
|
||||
};
|
||||
|
||||
private static final float DEFAULT_SEGMENT_SPEED = 1.0f;
|
||||
public static final int DEFAULT_EXERCISE_REPETITIONS = 10;
|
||||
public static final float DEFAULT_SEGMENT_SPEED = 1.0f;
|
||||
public static final float EXERCISE_ERROR_MARGIN = 1.0f;
|
||||
|
||||
/**
|
||||
* Function for sending an HTTP request to the server.
|
||||
@@ -95,7 +100,7 @@ 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.
|
||||
String[] leftRightData = content.get(PROPERTY_DATA).getAsString().split(";");
|
||||
String[] leftRightData = content.get(PROPERTY_PATH).getAsString().split(";");
|
||||
|
||||
if ( leftRightData.length != SENSOR_COUNT)
|
||||
return null;
|
||||
|
@@ -4,8 +4,7 @@ import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
import com.example.fitbot.R;
|
||||
import com.example.fitbot.ui.activities.FitnessActivity;
|
||||
import com.example.fitbot.ui.activities.MainActivity;
|
||||
import com.example.fitbot.util.NavigationManager;
|
||||
|
||||
public class EndScreenActivity extends AppCompatActivity {
|
||||
|
||||
@@ -14,7 +13,7 @@ public class EndScreenActivity extends AppCompatActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_end_screen);
|
||||
|
||||
com.example.fitbot.util.ButtonNavigation.setupButtonNavigation(this, R.id.homeButtonEndScreen, MainActivity.class);
|
||||
com.example.fitbot.util.ButtonNavigation.setupButtonNavigation(this, R.id.startButtonEndScreen, FitnessActivity.class);
|
||||
NavigationManager.setupButtonNavigation(this, R.id.homeButtonEndScreen, MainActivity.class);
|
||||
NavigationManager.setupButtonNavigation(this, R.id.startButtonEndScreen, FitnessActivity.class);
|
||||
}
|
||||
}
|
@@ -1,8 +1,6 @@
|
||||
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;
|
||||
@@ -14,7 +12,7 @@ import com.example.fitbot.R;
|
||||
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.NavigationManager;
|
||||
import com.example.fitbot.util.FitnessCycle;
|
||||
import com.example.fitbot.util.processing.InputProcessor;
|
||||
|
||||
@@ -56,7 +54,7 @@ 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);
|
||||
NavigationManager.setupButtonNavigation(this, R.id.homeButtonFitness, MainActivity.class);
|
||||
// Implement your logic when the robot focus is gained
|
||||
|
||||
}
|
||||
@@ -73,10 +71,7 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
|
||||
// This will provide the element with the appropriate dimensions for drawing
|
||||
// the canvas properly.
|
||||
personalMotionPreviewElement.post(() -> {
|
||||
Exercise exercise = this.acquireExercise();
|
||||
if ( exercise == null) {
|
||||
return;
|
||||
}
|
||||
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()};
|
||||
|
||||
@@ -87,8 +82,13 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
|
||||
|
||||
motionProcessor.startListening();
|
||||
motionProcessor.setInputHandler(personalMotionPreviewElement);
|
||||
}, (n) -> {
|
||||
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);
|
||||
|
||||
});
|
||||
});
|
||||
personalMotionPreviewElement.provideQiContext(qiContext);
|
||||
|
||||
// FitnessCycle.playVideo(qiContext, videoView, this);
|
||||
}
|
||||
@@ -97,28 +97,32 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
|
||||
* 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() {
|
||||
public void acquireExercise(Exercise.ExerciseFetchHandler onSuccessfulFetch, Exercise.ExerciseFetchHandler onFailedFetch) {
|
||||
// For some stupid reason we cannot perform network operations on the main thread.
|
||||
// therefore we'll have to do it like this...
|
||||
(new Thread(() -> {
|
||||
Exercise exercise = ExerciseManager.retrieveExercise();
|
||||
if ( exercise == null && this.qiContext != null)
|
||||
if ( exercise == 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);
|
||||
onFailedFetch.handle(null);
|
||||
}
|
||||
return exercise;
|
||||
else {
|
||||
onSuccessfulFetch.handle(exercise);
|
||||
}
|
||||
})).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRobotFocusLost() {
|
||||
// Implement your logic when the robot focus is lost
|
||||
QiSDK.unregister(this, this);
|
||||
// Go to the main screen
|
||||
NavigationManager.navigateToActivity(this, MainActivity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRobotFocusRefused(String reason) {
|
||||
// Implement your logic when the robot focus is refused
|
||||
QiSDK.unregister(this, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -127,6 +131,5 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
|
||||
QiSDK.unregister(this, this);
|
||||
this.motionProcessor.stopListening();
|
||||
this.motionProcessor = null;
|
||||
this.personalMotionPreviewElement.destroy();
|
||||
}
|
||||
}
|
@@ -5,7 +5,7 @@ import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import com.example.fitbot.R;
|
||||
import com.example.fitbot.util.ButtonNavigation;
|
||||
import com.example.fitbot.util.NavigationManager;
|
||||
|
||||
public class HelpActivity extends AppCompatActivity {
|
||||
|
||||
@@ -14,7 +14,7 @@ public class HelpActivity extends AppCompatActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_help);
|
||||
|
||||
ButtonNavigation.setupButtonNavigation(this, R.id.homeButtonHelp, MainActivity.class);
|
||||
NavigationManager.setupButtonNavigation(this, R.id.homeButtonHelp, MainActivity.class);
|
||||
|
||||
// Hide system UI
|
||||
hideSystemUI();
|
||||
|
@@ -10,13 +10,12 @@ import android.support.v4.widget.DrawerLayout;
|
||||
import android.support.v7.app.ActionBarDrawerToggle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
|
||||
import com.example.fitbot.R;
|
||||
import com.example.fitbot.util.ButtonNavigation;
|
||||
import com.example.fitbot.util.NavigationManager;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
@@ -63,8 +62,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().hide();
|
||||
}
|
||||
ButtonNavigation.setupButtonNavigation(this, R.id.startButtonMain, FitnessActivity.class);
|
||||
ButtonNavigation.setupButtonNavigation(this, R.id.helpButtonMain, HelpActivity.class);
|
||||
NavigationManager.setupButtonNavigation(this, R.id.startButtonMain, FitnessActivity.class);
|
||||
NavigationManager.setupButtonNavigation(this, R.id.helpButtonMain, HelpActivity.class);
|
||||
|
||||
/*---Tool Bar---*/
|
||||
setSupportActionBar(toolbar); // Make the toolbar act as the action bar
|
||||
|
@@ -2,7 +2,6 @@ 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.support.annotation.Nullable;
|
||||
@@ -15,6 +14,7 @@ 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.NavigationManager;
|
||||
import com.example.fitbot.util.FitnessCycle;
|
||||
import com.example.fitbot.util.processing.IInputHandler;
|
||||
import com.example.fitbot.util.processing.InputProcessor;
|
||||
@@ -105,25 +105,6 @@ public class PersonalMotionPreviewElement extends View implements IInputHandler
|
||||
projectVertex(new Vector3f(0, 0, -5.0f), getWidth(), getHeight()),
|
||||
projectVertex(new Vector3f(0, 0, 5.0f), getWidth(), getHeight())
|
||||
};
|
||||
}
|
||||
|
||||
public void destroy()
|
||||
{
|
||||
if ( this.motionProcessor != null )
|
||||
this.motionProcessor.stopListening();
|
||||
|
||||
this.motionProcessor = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
// Handler that is called every time the motion processor receives new data.
|
||||
this.motionProcessor.setInputHandler((rotationVector, deviceId) -> {
|
||||
@@ -133,27 +114,29 @@ public class PersonalMotionPreviewElement extends View implements IInputHandler
|
||||
|
||||
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
|
||||
this.destroy();
|
||||
Intent intent = new Intent(getContext(), MainActivity.class);
|
||||
getContext().startActivity(intent);
|
||||
NavigationManager.navigateToActivity(getContext(), MainActivity.class);
|
||||
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);
|
||||
this.parentActivity.acquireExercise((newExercise) -> {
|
||||
this.motionProcessor.useExercise(newExercise);
|
||||
}, (failed) -> {
|
||||
// Move to main screen
|
||||
NavigationManager.navigateToActivity(getContext(), MainActivity.class);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Finish the exercise.
|
||||
this.destroy();
|
||||
Intent intent = new Intent(getContext(), EndScreenActivity.class);
|
||||
getContext().startActivity(intent);
|
||||
NavigationManager.navigateToActivity(getContext(), EndScreenActivity.class);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -168,6 +151,17 @@ public class PersonalMotionPreviewElement extends View implements IInputHandler
|
||||
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;
|
||||
|
@@ -7,6 +7,8 @@ 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;
|
||||
@@ -17,12 +19,18 @@ 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
|
||||
@@ -59,28 +67,39 @@ public class FitnessCycle extends AppCompatActivity {
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
SayBuilder
|
||||
.with(ctx)
|
||||
.withLocale(locale)
|
||||
.withText(phrase)
|
||||
.build()
|
||||
.run();
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,4 +124,24 @@ public class FitnessCycle extends AppCompatActivity {
|
||||
Log.e("FitnessActivity", "VideoView is null. Check your layout XML.");
|
||||
}
|
||||
}
|
||||
|
||||
private static class PepperSpeechEntry {
|
||||
public String phrase;
|
||||
public QiContext context;
|
||||
public Locale locale;
|
||||
|
||||
public PepperSpeechEntry(String phrase, QiContext context, Locale locale) {
|
||||
this.phrase = phrase;
|
||||
this.context = context;
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
public Say getSay() {
|
||||
return SayBuilder
|
||||
.with(context)
|
||||
.withLocale(locale)
|
||||
.withText(phrase)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,7 +6,10 @@ import android.content.Intent;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
|
||||
public class ButtonNavigation {
|
||||
import com.example.fitbot.ui.activities.FitnessActivity;
|
||||
import com.example.fitbot.ui.activities.MainActivity;
|
||||
|
||||
public class NavigationManager {
|
||||
|
||||
/**
|
||||
* Sets up a button to navigate to a different activity when clicked.
|
||||
@@ -23,4 +26,15 @@ public class ButtonNavigation {
|
||||
currentActivity.finish();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the target activity.
|
||||
*
|
||||
* @param currentContext The current context
|
||||
* @param targetActivity The target activity
|
||||
*/
|
||||
public static void navigateToActivity(Context currentContext, Class<? extends Activity> targetActivity) {
|
||||
Intent intent = new Intent(currentContext, targetActivity);
|
||||
currentContext.startActivity(intent);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user