Cannot be tested on an emulated robot, requires a real robot.
Goal
In this tutorial, we will see how to save a specific location and how to make Pepper go to this location, using FreeFrames.
Prerequisites
Before stepping in this tutorial, you should:
Let’s start a new project
For further details, see: Creating a robot application.
To save locations, you will need the following fields in your MainActivity:
// Store the saved locations.
private val savedLocations = mutableMapOf<String, FreeFrame>()
// The QiContext provided by the QiSDK.
private var qiContext: QiContext? = null
// Store the Actuation service.
private var actuation: Actuation? = null
// Store the Mapping service.
private var mapping: Mapping? = null
// Store the saved locations.
private Map<String, FreeFrame> savedLocations = new HashMap<>();
// The QiContext provided by the QiSDK.
private QiContext qiContext;
// Store the Actuation service.
private Actuation actuation;
// Store the Mapping service.
private Mapping mapping;
In the onRobotFocusGained method, add the following code:
// Store the provided QiContext and services.
this.qiContext = qiContext;
actuation = qiContext.actuation
mapping = qiContext.mapping
// Store the provided QiContext and services.
this.qiContext = qiContext;
actuation = qiContext.getActuation();
mapping = qiContext.getMapping();
In the onRobotFocusLost method, add the following code:
// Remove the QiContext.
qiContext = null
// Remove the QiContext.
qiContext = null;
To save the robot current location, we will retrieve the robot frame, create a
FreeFrame at this location and finally store it inside a
Map<String, FreeFrame>.
Add this method to your MainActivity:
fun saveLocation(location: String) {
    // Get the robot frame asynchronously.
    val robotFrameFuture = actuation?.async()?.robotFrame()
    robotFrameFuture?.andThenConsume { robotFrame ->
        // Create a FreeFrame representing the current robot frame.
        val locationFrame: FreeFrame? = mapping?.makeFreeFrame()
        val transform: Transform = TransformBuilder.create().fromXTranslation(0.0)
        locationFrame.update(robotFrame, transform, 0L)
        // Store the FreeFrame.
        savedLocations.put(location, locationFrame);
    }
}
void saveLocation(final String location) {
    // Get the robot frame asynchronously.
    Future<Frame> robotFrameFuture = actuation.async().robotFrame();
    robotFrameFuture.andThenConsume(robotFrame -> {
        // Create a FreeFrame representing the current robot frame.
        FreeFrame locationFrame = mapping.makeFreeFrame();
        Transform transform = TransformBuilder.create().fromXTranslation(0);
        locationFrame.update(robotFrame, transform, 0L);
        // Store the FreeFrame.
        savedLocations.put(location, locationFrame);
    });
}
Now that we know how to save a location, we can make Pepper go to it.
We need to get the FreeFrame from the map and execute a GoTo
action with it.
Add a GoTo field in your MainActivity:
// Store the GoTo action.
private var goTo: GoTo? = null
// Store the GoTo action.
private GoTo goTo;
Add this method to your MainActivity:
fun goToLocation(location: String) {
    // Get the FreeFrame from the saved locations.
    val freeFrame: FreeFrame = savedLocations[location]
    // Extract the Frame asynchronously.
    val frameFuture: Future<Frame> = freeFrame.async().frame()
    frameFuture.andThenCompose { frame ->
        // Create a GoTo action.
        goTo = GoToBuilder.with(qiContext)
                .withFrame(frame)
                .build()
        // Display text when the GoTo action starts.
        goTo.addOnStartedListener { Log.i(TAG, "Moving...") }
        // Execute the GoTo action asynchronously.
        return goTo.async().run();
    }.thenConsume { future ->
        if (future.isSucces) {
            Log.i(TAG, "Location reached: $location")
        } else if (future.hasError) {
            Log.e(TAG, "Go to location error", future.error)
        }
    }
}
void goToLocation(final String location) {
    // Get the FreeFrame from the saved locations.
    FreeFrame freeFrame = savedLocations.get(location);
    // Extract the Frame asynchronously.
    Future<Frame> frameFuture = freeFrame.async().frame();
    frameFuture.andThenCompose(frame -> {
        // Create a GoTo action.
        goTo = GoToBuilder.with(qiContext)
                .withFrame(frame)
                .build();
        // Display text when the GoTo action starts.
        goTo.addOnStartedListener(() -> Log.i(TAG, "Moving..."));
        // Execute the GoTo action asynchronously.
        return goTo.async().run();
    }).thenConsume(future -> {
        if (future.isSuccess()) {
            Log.i(TAG, "Location reached: " + location);
        } else if (future.hasError()) {
            Log.e(TAG, "Go to location error", future.getError());
        }
    });
}
Do not forget to remove this listener on GoTo in the
onRobotFocusLost method:
// Remove on started listeners from the GoTo action.
goTo?.removeAllOnStartedListeners()
// Remove on started listeners from the GoTo action.
if (goTo != null) {
    goTo.removeAllOnStartedListeners();
}
We will implement this functionality using:
add_item_edit to enter the location name,Button to save the location,Spinner to display the saved locations and the selected one,Button to make Pepper move to the selected location.Modify your activity_main.xml file with the following code:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:focusableInTouchMode="true"
    tools:context=".MainActivity">
    <Button
        android:id="@+id/save_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/save"
        app:layout_constraintRight_toLeftOf="@+id/add_item_edit"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintBottom_toTopOf="@+id/goto_button"
        app:layout_constraintTop_toTopOf="parent" />
    <Button
        android:id="@+id/goto_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/go_to_text"
        app:layout_constraintLeft_toLeftOf="@+id/save_button"
        app:layout_constraintRight_toRightOf="@+id/save_button"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/save_button" />
    <EditText
        android:id="@+id/add_item_edit"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="10"
        android:inputType="text"
        android:labelFor="@+id/add_item_edit"
        android:hint="@string/location"
        tools:text="Location"
        app:layout_constraintBaseline_toBaselineOf="@+id/save_button"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintLeft_toRightOf="@+id/save_button" />
    <Spinner
        android:id="@+id/spinner"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="@+id/add_item_edit"
        app:layout_constraintRight_toRightOf="@+id/add_item_edit"
        app:layout_constraintTop_toTopOf="@+id/goto_button"
        app:layout_constraintBottom_toBottomOf="@+id/goto_button"
        app:layout_constraintHorizontal_bias="0.0" />
</android.support.constraint.ConstraintLayout>
Add the following fields in your MainActivity:
private lateinit var spinnerAdapter: ArrayAdapter<String>
// Store the selected location.
private var selectedLocation: String? = null
private Button gotoButton;
private Button saveButton;
private ArrayAdapter<String> spinnerAdapter;
// Store the selected location.
private String selectedLocation;
And add this code in the onCreate method:
// Save location on save button clicked.
save_button.setOnClickListener {
    val location: String = add_item_edit.text.toString()
    add_item_edit.text.clear()
    // Save location only if new.
    if (location.isNotEmpty() && !savedLocations.containsKey(location)) {
        spinnerAdapter.add(location)
        saveLocation(location)
    }
}
// Go to location on go to button clicked.
goto_button.setOnClickListener {
    selectedLocation?.let {
        goto_button.isEnabled = false
        save_button.isEnabled = false
        goToLocation(it)
    }
}
 spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
    override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
        selectedLocation = parent.getItemAtPosition(position) as String
        Log.i(TAG, "onItemSelected: $selectedLocation")
    }
    override fun onNothingSelected(parent: AdapterView<*>) {
        selectedLocation = null
        Log.i(TAG, "onNothingSelected")
    }
}
// Setup spinner adapter.
spinnerAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, ArrayList())
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = spinnerAdapter
final EditText addItemEdit = (EditText) findViewById(R.id.add_item_edit);
final Spinner spinner = (Spinner) findViewById(R.id.spinner);
// Save location on save button clicked.
saveButton = (Button) findViewById(R.id.save_button);
saveButton.setOnClickListener(v -> {
    String location = addItemEdit.getText().toString();
    addItemEdit.setText("");
    // Save location only if new.
    if (!location.isEmpty() && !savedLocations.containsKey(location)) {
        spinnerAdapter.add(location);
        saveLocation(location);
    }
});
// Go to location on go to button clicked.
gotoButton = (Button) findViewById(R.id.goto_button);
gotoButton.setOnClickListener(v -> {
    if (selectedLocation != null) {
        gotoButton.setEnabled(false);
        saveButton.setEnabled(false);
        goToLocation(selectedLocation);
    }
});
// Store location on selection.
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        selectedLocation = (String) parent.getItemAtPosition(position);
        Log.i(TAG, "onItemSelected: " + selectedLocation);
    }
    @Override
    public void onNothingSelected(AdapterView<?> parent) {
        selectedLocation = null;
        Log.i(TAG, "onNothingSelected");
    }
});
// Setup spinner adapter.
spinnerAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, new ArrayList<String>());
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(spinnerAdapter);
When Pepper is ready to save locations, we want to enable the UI elements.
Add this method to your MainActivity:
private fun waitForInstructions() {
    Log.i(TAG, "Waiting for instructions...")
    runOnUiThread {
        save_button.isEnabled = true
        goto_button.isEnabled = true
    }
}
private void waitForInstructions() {
    Log.i(TAG, "Waiting for instructions...");
    runOnUiThread(() -> {
        saveButton.setEnabled(true);
        gotoButton.setEnabled(true);
    });
}
Call this method at the end of onRobotFocusGained:
// Store the provided QiContext and services.
this.qiContext = qiContext
actuation = qiContext.actuation
mapping = qiContext.mapping
waitForInstructions()
// Store the provided QiContext and services.
this.qiContext = qiContext;
actuation = qiContext.getActuation();
mapping = qiContext.getMapping();
waitForInstructions();
And in the .thenConsume(...) inside the goToLocation method:
if (future.isSuccess) {
    Log.i(TAG, "Location reached: $location")
    waitForInstructions()
} else if (future.hasError()) {
    Log.e(TAG, "Go to location error", future.error)
    waitForInstructions()
}
if (future.isSuccess()) {
    Log.i(TAG, "Location reached: " + location);
    waitForInstructions();
} else if (future.hasError()) {
    Log.e(TAG, "Go to location error", future.getError());
    waitForInstructions();
}
In this tutorial, the locations persistence is scoped to the Activity lifetime.
If you need to make these locations persist for a longer time, you can:
Localize the robot (see Localize).
Create the locations (FreeFrame).
Compute the Transform between each FreeFrame and the map frame:
val locationFrame: FreeFrame = ...
val mapFrame: Frame = qiContext.mapping.mapFrame()
val transform: Transform = locationFrame.frame().computeTransform(mapFrame).transform
FreeFrame locationFrame = ...;
Frame mapFrame = qiContext.getMapping().mapFrame();
Transform transform = locationFrame.frame().computeTransform(mapFrame).getTransform();
Serialize each Transform (for example in json).
Save each serialized Transform data (for example in file, database, server, …).
If you want to reuse them later, you can:
Retrieve each serialized Transform data.
Deserialize each data into a Transform.
Localize the robot (see Localize).
Create each FreeFrame with the corresponding Transform:
val transform: Transform = ...
val mapping: Mapping = qiContext.mapping
val mapFrame: Frame = mapping.mapFrame()
val locationFrame: FreeFrame = mapping.makeFreeFrame()
locationFrame.update(mapFrame, transform, 0L)
Transform transform = ...;
Mapping mapping = qiContext.getMapping();
Frame mapFrame = mapping.mapFrame();
FreeFrame locationFrame = mapping.makeFreeFrame();
locationFrame.update(mapFrame, transform, 0L);
 The sources for this tutorial are available on GitHub.
| Step | Action | 
|---|---|
Install and run the application. For further details, see: Running an application.  | 
|
| Choose “Saving locations”. | |
Enter “Room” in the  The   | 
|
| Open the robot hatch and move him somewhere else. Then close his hatch. | |
Enter “Kitchen” in the  The   | 
|
Select “Room” on the  Pepper will go to the “Room” location. When he arrives, the log trace “Location reached: Room” is displayed in the console.  
 | 
You are now able to store locations and make Pepper go to them!