Added Pepper abstraction for synchronous events to prevent app from crashing

This commit is contained in:
Luca Warmenhoven
2024-05-30 16:38:48 +02:00
parent a0988bf8cc
commit f7e96cf356
9 changed files with 264 additions and 22 deletions

View File

@@ -0,0 +1,15 @@
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

@@ -0,0 +1,10 @@
package com.example.fitbot.pepper;
/**
* Enum for Pepper actions
*/
public enum EPepperAction {
ACTION_SPEAK,
ACTION_ANIMATE,
NO_ACTION;
}

View File

@@ -0,0 +1,113 @@
package com.example.fitbot.pepper;
import com.aldebaran.qi.sdk.QiContext;
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 java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Class representing the Pepper robot.
* All functionality related to Pepper is implemented in this class.
*/
public class Pepper {
private static final ConcurrentLinkedQueue<AbstractPepperActionEvent> pepperActionEventQueue =
new ConcurrentLinkedQueue<>();
private static final AtomicBoolean isAnimating = new AtomicBoolean(false);
private static final AtomicBoolean isSpeaking = new AtomicBoolean(false);
private static final Locale DEFAULT_LOCALE = new Locale(Language.DUTCH, Region.NETHERLANDS);
/**
* Speak a phrase with Pepper.
*
* @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));
}
/**
* Animate Pepper with the given animation.
*
* @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));
}
/**
* Add an event to the event queue.
* The event will be executed once Pepper is ready to process it.
*
* @param event The event to add to the queue
*/
public static void addToEventQueue(AbstractPepperActionEvent event)
{
pepperActionEventQueue.add(event);
processEventQueue();
}
/**
* Process the event queue.
* This method will process the next event in the queue if Pepper is not currently
* speaking or animating.
* 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())
{
AbstractPepperActionEvent event = pepperActionEventQueue.poll();
if (event == null)
return;
switch (event.getAction())
{
/* Event for speaking a phrase **/
case ACTION_SPEAK:
if ( !(event instanceof PepperSpeechEvent) || isSpeaking.get())
break;
PepperSpeechEvent speechEvent = (PepperSpeechEvent) event;
isSpeaking.set(true);
speechEvent
.sayPhrase()
.async()
.run()
.andThenConsume(future -> {
isSpeaking.set(false);
processEventQueue();
});
break;
/* Event for animating the robot **/
case ACTION_ANIMATE:
if ( !(event instanceof PepperAnimationEvent) || isAnimating.get())
break;
PepperAnimationEvent animationEvent = (PepperAnimationEvent) event;
animationEvent
.getAnimation()
.async()
.run()
.andThenConsume(future -> {
isAnimating.set(false);
processEventQueue();
});
break;
default:
break;
}
}
}
}

View File

@@ -0,0 +1,37 @@
package com.example.fitbot.pepper;
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;
public class PepperAnimationEvent extends AbstractPepperActionEvent {
public final String animationName;
public PepperAnimationEvent(QiContext context, String animationName) {
super(context);
this.animationName = animationName;
}
@Override
public EPepperAction getAction() {
return null;
}
/**
* 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()))
.build();
return AnimateBuilder.with(this.context)
.withAnimation(animation)
.build();
}
}

View File

@@ -0,0 +1,35 @@
package com.example.fitbot.pepper;
import com.aldebaran.qi.sdk.QiContext;
import com.aldebaran.qi.sdk.builder.SayBuilder;
import com.aldebaran.qi.sdk.object.conversation.Say;
import com.aldebaran.qi.sdk.object.locale.Locale;
public class PepperSpeechEvent extends AbstractPepperActionEvent {
public final String phrase;
public final Locale locale;
public PepperSpeechEvent(QiContext context, String phrase, Locale locale)
{
super(context);
this.locale = locale;
this.phrase = phrase;
}
@Override
public EPepperAction getAction() {
return EPepperAction.ACTION_SPEAK;
}
/**
* Returns a Say object, which can then be executed.
*/
public Say sayPhrase()
{
return SayBuilder.with(this.context)
.withText(this.phrase)
.withLocale(this.locale)
.build();
}
}

View File

@@ -11,6 +11,7 @@ import com.aldebaran.qi.sdk.design.activity.conversationstatus.SpeechBarDisplayS
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.util.NavigationManager;
import com.example.fitbot.util.FitnessCycle;
@@ -31,7 +32,7 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
private static final String[] EXERCISE_NOT_FOUND_MESSAGES = new String[] {
"Ik heb momenteel helaas wat moeite met het ophalen van oefeningen, sorry.",
"Het lijkt erop dat de oefeningen op een misterieus avontuur zijn. Even wachten tot ze terug zijn.",
"Ssst, de oefeningen slapen nog, probeer het later nog eens."
", de oefeningen slapen nog, probeer het later nog eens."
};
private static final String EXERCISE_NOT_FOUND_SEEK_HELP_MESSAGE =
@@ -84,9 +85,9 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
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);
Pepper.speak(EXERCISE_NOT_FOUND_MESSAGES[randomMessageIndex], qiContext);
Pepper.speak(EXERCISE_NOT_FOUND_SEEK_HELP_MESSAGE, qiContext);
NavigationManager.navigateToActivity(this, MainActivity.class);
});
});
@@ -121,15 +122,15 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
}
@Override
public void onRobotFocusRefused(String reason) {
QiSDK.unregister(this, this);
}
public void onRobotFocusRefused(String reason) {}
@Override
protected void onDestroy() {
super.onDestroy();
if ( this.motionProcessor != null ) {
this.motionProcessor.stopListening();
this.motionProcessor = null;
}
QiSDK.unregister(this, this);
this.motionProcessor.stopListening();
this.motionProcessor = null;
super.onDestroy();
}
}

View File

@@ -130,13 +130,13 @@ public class PersonalMotionPreviewElement extends View implements IInputHandler
this.motionProcessor.useExercise(newExercise);
}, (failed) -> {
// Move to main screen
NavigationManager.navigateToActivity(getContext(), MainActivity.class);
NavigationManager.navigateToActivity(parentActivity, MainActivity.class);
});
}
else
{
// Finish the exercise.
NavigationManager.navigateToActivity(getContext(), EndScreenActivity.class);
NavigationManager.navigateToActivity(parentActivity, EndScreenActivity.class);
return;
}
}

View File

@@ -125,12 +125,18 @@ public class FitnessCycle extends AppCompatActivity {
}
}
private static class PepperSpeechEntry {
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;
@@ -144,4 +150,15 @@ public class FitnessCycle extends AppCompatActivity {
.build();
}
}
private enum PepperAction {
SPEAK,
ANIMATE,
MOVE
}
private static class PepperActionEvent {
public PepperAction action;
public Object data;
}
}

View File

@@ -20,21 +20,35 @@ public class NavigationManager {
*/
public static void setupButtonNavigation(Activity currentActivity, int buttonId, Class<? extends Activity> targetActivity) {
Button button = currentActivity.findViewById(buttonId);
button.setOnClickListener(v -> {
Intent intent = new Intent(currentActivity, targetActivity);
currentActivity.startActivity(intent);
currentActivity.finish();
});
if ( button == null )
{
throw new IllegalArgumentException("Button with ID " + buttonId + " not found in " + currentActivity.getClass().getSimpleName());
}
button.setOnClickListener(v -> NavigationManager.navigateToActivity(currentActivity, targetActivity));
}
/**
* Navigates to the target activity.
*
* @param currentContext The current context
* @param currentActivity The current activity
* @param targetActivity The target activity
*/
public static void navigateToActivity(Context currentContext, Class<? extends Activity> targetActivity) {
Intent intent = new Intent(currentContext, targetActivity);
currentContext.startActivity(intent);
public static void navigateToActivity(Activity currentActivity, Class<? extends Activity> targetActivity) {
Intent intent = new Intent(currentActivity, targetActivity);
// Close previous activity
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
currentActivity.startActivity(intent);
currentActivity.finish();
}
/**
* Navigates to the target activity.
*
* @param context The context
* @param targetActivity The target activity
*/
public static void navigateToActivity(Context context, Class<? extends Activity> targetActivity) {
Intent intent = new Intent(context, targetActivity);
context.startActivity(intent);
}
}