Running Actions on Pepper

Actions are the main components you will use to create applications for Pepper. With actions, Pepper can:

talk

talk

listen

listen

move

move

perform animations

perform animations

engage humans

engage humans

chat

chat

turn his gaze somewhere

turn his gaze somewhere

localize himself in an environment

localize himself in an environment

take pictures

take pictures

Prerequisites

In order to understand how to use actions, you should be familiar with asynchronicity.

Create an action

Create actions synchronously or asynchronously using builders.

Here are the steps to build an action:

  • create an action builder with the QiContext,
  • pass the action parameters to the builder,
  • call the build or buildAsync method to create the action.

Synchronously

Call the build method on the builder to create an action synchronously:

// Build an action synchronously.
val say: Say = SayBuilder.with(qiContext) // Create a builder with the QiContext.
                    .withText("Hello!") // Specify the action parameters.
                    .build()
// Build an action synchronously.
Say say = SayBuilder.with(qiContext) // Create a builder with the QiContext.
                    .withText("Hello!") // Specify the action parameters.
                    .build();

Warning

Do not create actions synchronously on the UI thread or it will throw a NetworkOnMainThreadException. This mechanism prevents the UI thread to be blocked.

Asynchronously

Call the buildAsync method on the builder to create an action asynchronously:

// Build an action asynchronously.
val sayActionFuture: Future<Say> = SayBuilder.with(qiContext) // Create a builder with the QiContext.
                                .withText("Hello!") // Specify the action parameters.
                                .buildAsync()
// Build an action asynchronously.
Future<Say> sayActionFuture = SayBuilder.with(qiContext) // Create a builder with the QiContext.
                                        .withText("Hello!") // Specify the action parameters.
                                        .buildAsync();

The reference on the action is a Future instance.

To learn more about the Future class, see Chaining operations.

Execute an action

Synchronously

To execute a synchronous action, you use the run method on it:

// Execute the action synchronously.
say.run()
// Execute the action synchronously.
say.run();

Warning

Do not call the run method on the UI thread or it will throw a NetworkOnMainThreadException. This mechanism prevents the UI thread to be blocked.

Asynchronously

Each action provides an Async interface which allows you to create an asynchronous action. To create an asynchronous action, call the async method on the action:

// Create an asynchronous action.
val sayAsync: Say.Async = say.async()
// Create an asynchronous action.
Say.Async sayAsync = say.async();

An asynchronous action is executed when you call the run method on it:

// Execute the asynchronous action.
sayAsync.run()
// Execute the asynchronous action.
sayAsync.run();

Or in one line:

// Execute the action asynchronously.
say.async().run()
// Execute the action asynchronously.
say.async().run();

As a result:

  • the thread is not blocked when the action runs,
  • the action execution may not be finished immediately after the run call.

The choice between asynchronicity and synchronicity depends on your use case. Some use cases will be detailed below.


Manage the action execution

As you have seen above, actions can be performed synchronously or asynchronously.

Consequently, this allows you to do the following:

  • execute some code when an action execution succeeds,
  • handle potential errors during an action execution,
  • handle an action cancellation.

Get an action result / Act on an action success

Now that you know how to execute an action, you will see how to act when this execution succeeds.

Synchronously

Use the run method to run an action synchronously and get the action result.

// Run the action synchronously and get the result.
val ignore = say.run()
// Act on success.
Log.i(TAG, "Say action finished with success.")
// Run the action synchronously and get the result.
Void ignore = say.run();
// Act on success.
Log.i(TAG, "Say action finished with success.");

Note

Here the result type is Void because the Say does not have any value to return. Other actions have a meaningful value that you are able to use.


Asynchronously

To act on an asynchronous action execution, you need to keep a reference on it. For an asynchronous action, the run method returns that reference for you:

val sayFuture: Future<Void> = say.async().run()
Future<Void> sayFuture = say.async().run();

The Future class provides the andThenConsume method that allows you to get the value contained in the Future and consume it:

// Run the action asynchronously.
val sayFuture: Future<Void> = say.async().run()

// Chain a lambda to the future.
sayFuture.andThenConsume {
    // Act on success.
    Log.i(TAG, "Say action finished with success.")
}
 // Run the action asynchronously.
Future<Void> sayFuture = say.async().run();

// Chain a lambda to the future.
sayFuture.andThenConsume(ignore -> {
    // Act on success.
    Log.i(TAG, "Say action finished with success.");
})

Note

You may want to run your actions asynchronously if you are on the UI thread. For example, you might want Pepper to talk when you click on a button.

The lambda is not executed on the UI thread, so if you want to execute some code interacting with the UI in this lambda, use the Qi.onUiThread method. This method simply transfers the execution of the lambda on the UI thread.

sayFuture.andThenConsume(Qi.onUiThread(Consumer {
    Toast.makeText(MainActivity.this, "Say action finished with success.", Toast.LENGTH_SHORT).show()
}))
sayFuture.andThenConsume(Qi.onUiThread((Consumer<Void>) ignore -> {
    Toast.makeText(MainActivity.this, "Say action finished with success.", Toast.LENGTH_SHORT).show();
}));

Handle errors

When you execute an action, keep in mind that an error can occur. The way to handle it depends upon the way you execute the action.

Synchronously

When you call the run method on a synchronous action, a RuntimeException can be thrown. If this occurs in the onRobotFocusGained method, its execution stops at the run method call.

Manage the error using a try-catch block:

try {
    say.run()
    Log.i(TAG, "Success")
} catch (exception: Exception) {
    Log.e(TAG, "Error", exception)
}
try {
    say.run();
    Log.i(TAG, "Success");
} catch (Exception exception) {
    Log.e(TAG, "Error", exception);
}

Asynchronously

If you choose to execute an action asynchronously, use the thenConsume method to handle a potential error.

The Future class provides the hasError method to check if the Future contains an error:

val sayFuture = say.async().run()
sayFuture.thenConsume { future ->
    if (future.isSuccess) {
        Log.i(TAG, "Success")
    } else if (future.hasError()) {
        Log.e(TAG, "Error", future.error)
    }
}
Future<Void> sayFuture = say.async().run();
sayFuture.thenConsume(future -> {
    if (future.isSuccess()) {
        Log.i(TAG, "Success");
    } else if (future.hasError()) {
        Log.e(TAG, "Error", future.getError());
    }
});

Cancel actions

Cancel an action by calling the requestCancellation method on the corresponding Future:

// Execute the action asynchronously.
val sayFuture: Future<Void> = say.async().run()
// Cancel the action asynchronously.
sayFuture.requestCancellation()
// Execute the action asynchronously.
Future<Void> sayFuture = say.async().run();
// Cancel the action asynchronously.
sayFuture.requestCancellation();

If you use the thenConsume method to chain the Future, you can check if the Future is cancelled with the isCancelled method:

// Execute the action asynchronously.
val sayFuture: Future<Void> = say.async().run()

// Chain the future with a lambda.
sayFuture.thenConsume { future ->
    if (future.isSuccess) {
        Log.i(TAG, "Success")
    } else if (future.isCancelled) {
        Log.i(TAG, "Cancelled")
    }
}

// Cancel the action asynchronously.
sayFuture.requestCancellation()
// Execute the action asynchronously.
Future<Void> sayFuture = say.async().run();

// Chain the future with a lambda.
sayFuture.thenConsume(future -> {
    if (future.isSuccess()) {
        Log.i(TAG, "Success");
    } else if (future.isCancelled()) {
        Log.i(TAG, "Cancelled");
    }
});

// Cancel the action asynchronously.
sayFuture.requestCancellation();

Use multiple actions

Now that you have seen how to act on an action execution, we will cover a common use case: how to use multiple actions together.

Chain multiple actions

Synchronously

To chain actions synchronously, execute them synchronously one after the other:

// Create the first action.
val sayHello: Say = SayBuilder.with(qiContext)
                         .withText("Hello!")
                         .build()

// Create the second action.
val sayPepper: Say = SayBuilder.with(qiContext)
                          .withText("I'm Pepper!")
                          .build()

// Run the first action synchronously.
sayHello.run()
// Run the second action synchronously.
sayPepper.run()
// Create the first action.
Say sayHello = SayBuilder.with(qiContext)
                         .withText("Hello!")
                         .build();

// Create the second action.
Say sayPepper = SayBuilder.with(qiContext)
                          .withText("I'm Pepper!")
                          .build();

// Run the first action synchronously.
sayHello.run();
// Run the second action synchronously.
sayPepper.run();

If the first action succeeds, the second one will be executed.

Asynchronously

The Future class provides 2 methods to chain multiple actions asynchronously: thenCompose and andThenCompose:

// Create the first action.
val sayHello: Say = SayBuilder.with(qiContext)
                         .withText("Hello!")
                         .build()

// Run the first action asynchronously.
val sayHelloFuture: Future<Void> = sayHello.async().run()

// Chain a lambda to the future.
sayHelloFuture.andThenCompose {
    // Create the second action.
    val sayPepper: Say = SayBuilder.with(qiContext)
                              .withText("I'm Pepper!")
                              .build()

    // Run the second action asynchronously.
    return sayPepper.async().run()
}
// Create the first action.
Say sayHello = SayBuilder.with(qiContext)
                         .withText("Hello!")
                         .build();

// Run the first action asynchronously.
Future<Void> sayHelloFuture = sayHello.async().run();

// Chain a lambda to the future.
sayHelloFuture.andThenCompose(ignore -> {
    // Create the second action.
    Say sayPepper = SayBuilder.with(qiContext)
                              .withText("I'm Pepper!")
                              .build();

    // Run the second action asynchronously.
    return sayPepper.async().run();
});

Run actions simultaneously

If you want Pepper to perform multiple actions at the same time, execute them asynchronously one after the other:

 // Create the first action.
val say: Say = SayBuilder.with(qiContext)
                    .withText("Hello!")
                    .build()

// Create the second action.
val animate: Animate = AnimateBuilder.with(qiContext)
                                .withAnimation(animation)
                                .build()

// Run the first action asynchronously.
say.async().run()
// Run the second action asynchronously.
animate.async().run()
// Create the first action.
Say say = SayBuilder.with(qiContext)
                    .withText("Hello!")
                    .build();

// Create the second action.
Animate animate = AnimateBuilder.with(qiContext)
                                .withAnimation(animation)
                                .build();

// Run the first action asynchronously.
say.async().run();
// Run the second action asynchronously.
animate.async().run();

Note

Some actions cannot be performed simultaneously. For example, the robot cannot run multiple Say instances at the same time.