Advertisement
  1. Code
  2. Android SDK

Android Things: Integrando el Asistente de Google

Scroll to top
Read Time: 16 min

() translation by (you can also view the original English article)

Con el crecimiento de la Internet de las Cosas (IoT, del inglés Internet of Things), desarrolladores a ingenieros tuvieron que repensar cómo interactúan los usuarios con sus dispositivos en el día a día.

Si bien las pantallas funcionan bien con los sitios web y la mayoría de las apps, los dispositivos que se comunican con el mundo real pueden resultar un poco más tediosos de operar si tienes que utilizar múltiples botones o una pantalla para hacerlos funcionar. Una de las maneras de resolver esto es habilitar comandos de voz en tus dispositivos.

En este tutorial aprenderás acerca del Asistente de Google y cómo puedes integrarlo con tus dispositivos IoT.

Si necesitas un poco de contexto acerca de Android Things antes de comenzar, revisa algunos de mis otros posts aquí en Envato Tuts+.

SDK del Asistente

El SDK del Asistente de Google te permite añadir comandos de voz con detección de palabras clave, procesamiento de lenguajes naturales, y otras funciones de aprendizaje de máquinas a tus dispositivos IoT. Es mucho lo que se puede hacer con el SDK del Asistente, pero este tutorial sólo se enfocará en lo básico: cómo puedes integrarlo en tus dispositivos IoT para poder hacer preguntas, obtener información, e interactuar con la funcionalidad estándar del Asistente.

En cuanto a los requisitos de hardware, tienes algunas opciones. Puedes utilizar un Raspberry Pi con Android Things instalado, en conjunto con un Kit de Voz AIY.

O puedes emplear un parlante estándar con un conector AUX y un micrófono USB.

Además, puedes utilizar cualquier otra configuración de hardware I²S. Aunque no discutiremos I²S en detalle en este tutorial, vale la pena destacar que el Kit de Voz utilizará este protocolo. Una vez que tengas tu micrófono y tu parlante preparados, también necesitarás agregarle un botón a tu dispositivo. Este botón deberá registrar dos estados: presionado y soltado. Puedes lograr esto con un botón de arcade, o un botón estándar con un botón estándar con una resistencia pull-down conectada a uno de los polos.

Credenciales

Una vez que hayas configurado tu hardware, es hora de integrar el SDK del Asistente a tu dispositivo. Primero, necesitarás crear un nuevo archivo de credenciales para tu dispositivo. Puedes leer las instrucciones para esto en la documentación del Asistente de Google. Una vez que tengas tu archivo credentials.json, debes colocarlo dentro del directorio res/raw de tu módulo de Android Things.

credentialsjson file in the resraw directorycredentialsjson file in the resraw directorycredentialsjson file in the resraw directory

Luego de crear tus credenciales con Google, necesitarás declarar algunos permisos para tu app. Abre el archivo AndroidManifest.xml y escribe las siguientes líneas dentro de la etiqueta manifest, pero antes de la etiqueta application.

1
<uses-permission android:name="android.permission.RECORD_AUDIO" />
2
 <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
3
 <uses-permission android:name="android.permission.INTERNET" />
4
 <uses-permission android:name="com.google.android.things.permission.MANAGE_AUDIO_DRIVERS" />

Vale la pena destacar que tendrás que reiniciar tu dispositivo luego de instalar la app con estos permisos para que puedan ser administrados.

Luego, necesitas copiar el módulo gRPC en tu app para que se comunique con el dispositivo. Esto puede tornarse un poco complicado, por lo cual el mejor lugar para descargarlo es de la app de ejemplo de Google Assistant Android Things, que puede encontrarse en la cuenta de GitHub de Android Things. Luego, deberás actualizar tu archivo settings.gradle para que incluya el nuevo módulo.

1
include ':mobile', ':things', ':grpc'

Luego de actualizar settings.gradle, incluye el módulo como una dependencia de tu módulo things, escribiendo la siguiente línea en el archivo build.gradle del módulo things, e incluyendo el driver del botón de Google (lo necesitarás para activar el micrófono), y de manera opcional el driver de Voice Hat si estás usando ese hardware.

1
compile project(':grpc')
2
 compile 'com.google.android.things.contrib:driver-button:0.4'
3
 
4
 //optional

5
 compile 'com.google.android.things.contrib:driver-voicehat:0.2'

También necesitarás incluir protobuf como dependencia en tu archivo build.gradle ubicado a nivel de proyecto.

1
classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.0"

Luego, incluye la librería oauth2 en tu proyecto, abriendo el archivo build.gradle del módulo things y escribiendo lo siguiente dentro del nodo dependencies:

1
compile('com.google.auth:google-auth-library-oauth2-https:0.6.0') {
2
     exclude group: 'org.apache.httpcomponents', module: 'httpclient'
3
 }

Puedes hallar conflictos aquí si tu proyecto contiene la dependencia Espresso, con un mensaje de error similar al siguiente:

1
Warning:Conflict with dependency 'com.google.code.findbugs:jsr305' in project ':things'. Resolved versions for app (1.3.9) and test app (2.0.1) differ. See http://g.co/androidstudio/app-test-app-conflict for details. 

De ser así, simplemente borra la dependencia Espresso de build.gradle.

Luego de sincronizar tu proyecto, crea una clase nueva llamada Credentials.java para acceder a tus credenciales.

1
public class Credentials {
2
  static UserCredentials fromResource(Context context, int resourceId)
3
  throws IOException, JSONException {
4
         InputStream is = context.getResources().openRawResource(resourceId);
5
         byte[] bytes = new byte[is.available()];
6
         is.read(bytes);
7
         JSONObject json = new JSONObject(new String(bytes, "UTF-8"));
8
             return new UserCredentials(json.getString("client_id"),
9
                 json.getString("client_secret"),
10
                 json.getString("refresh_token")
11
             );
12
         }
13
     }
14
 }

Clase Auxiliar Embedded Assistant

Una vez creada la clase Credentials.java, es momento de crear una nueva clase llamada EmbeddedAssistant.java. Esta es una clase auxiliar originalmente escrita por ingenieros de Google para integrar de manera sencilla el Asistente de Google para Android Things. Aunque esta clase es muy fácil de utilizar, simplemente incluyéndola en tu proyecto, queremos adentrarnos en ella y comprender cómo funciona.

Lo primero que harás es crear dos clases abstractas internas que servirán para manejar callbacks en la conversación y peticiones a la API del Asistente.

1
public class EmbeddedAssistant {
2
 
3
     public static abstract class RequestCallback {
4
         public void onRequestStart() {}
5
         public void onAudioRecording() {}
6
         public void onSpeechRecognition(String utterance) {}
7
     }
8
 
9
     public static abstract class ConversationCallback {
10
       public void onResponseStarted() {}
11
 	    public void onResponseFinished() {}
12
 	    public void onConversationEvent(EventType eventType) {}
13
 	    public void onAudioSample(ByteBuffer audioSample) {}
14
 	    public void onConversationError(Status error) {}
15
 	    public void onError(Throwable throwable) {}
16
 	    public void onVolumeChanged(int percentage) {}
17
 	    public void onConversationFinished() {}
18
 	}
19
 }

Una vez que tus dos clases internas estén listas, continúa definiendo el siguiente conjunto de variables globales al principio de tu clase. La mayor parte de estas serán inicializadas luego en este archivo. Estos valores se usan para mantener registro del estado del dispositivo y sus interacciones con la API del Asistente. 

1
private static final String ASSISTANT_API_ENDPOINT = "embeddedassistant.googleapis.com";
2
 private static final int AUDIO_RECORD_BLOCK_SIZE = 1024;
3
 
4
 private RequestCallback mRequestCallback;
5
 private ConversationCallback mConversationCallback;
6
 
7
 //Used for push-to-talk functionality

8
 private ByteString mConversationState;
9
 private AudioInConfig mAudioInConfig;
10
 private AudioOutConfig mAudioOutConfig;
11
 private AudioTrack mAudioTrack;
12
 private AudioRecord mAudioRecord;
13
 private int mVolume = 100; // Default to maximum volume.

14
 
15
 private UserCredentials mUserCredentials;
16
 
17
 private MicrophoneMode mMicrophoneMode;
18
 private HandlerThread mAssistantThread;
19
 private Handler mAssistantHandler;
20
 
21
 // gRPC client and stream observers.

22
 private int mAudioOutSize; // Tracks the size of audio responses to determine when it ends.

23
 private EmbeddedAssistantGrpc.EmbeddedAssistantStub mAssistantService;
24
 private StreamObserver<ConverseRequest> mAssistantRequestObserver;
25
 

Manejando las Respuestas de la API

Mientras que el código anterior tiene un objeto StreamObserver<ConverseRequest> para las peticiones a la API del Asistente, también necesitarás uno para las respuestas. Este objeto consistirá de una cláusula switch que chequea el estado de la respuesta y luego la maneja de acuerdo a este.

1
private StreamObserver<ConverseResponse> mAssistantResponseObserver =
2
     new StreamObserver<ConverseResponse>() {
3
         @Override
4
         public void onNext(ConverseResponse value) {
5
             switch (value.getConverseResponseCase()) {

El primer caso chequea que un usuario ha terminado de hablar y utiliza ConversationCallback para hacerle saber al resto de la clase que una respuesta es inminente.

1
case EVENT_TYPE:
2
     mConversationCallback.onConversationEvent(value.getEventType());
3
     if (value.getEventType() == EventType.END_OF_UTTERANCE) {
4
         mConversationCallback.onResponseStarted();
5
     }
6
     break;

El siguiente caso comprobará y actualizará la conversación, el volumen, y el estado del micrófono.

1
case RESULT:
2
     // Update state.

3
     mConversationState = value.getResult().getConversationState();
4
     
5
     // Update volume.

6
     if (value.getResult().getVolumePercentage() != 0) {
7
         int volumePercentage = value.getResult().getVolumePercentage();
8
         mVolume = volumePercentage;
9
         mAudioTrack.setVolume(AudioTrack.getMaxVolume()
10
                 * volumePercentage / 100.0f);
11
         mConversationCallback.onVolumeChanged(volumePercentage);
12
     }
13
     
14
     if (value.getResult().getSpokenRequestText() != null &&
15
             !value.getResult().getSpokenRequestText().isEmpty()) {
16
         mRequestCallback.onSpeechRecognition(value.getResult()
17
                 .getSpokenRequestText());
18
     }
19
     
20
     // Update microphone mode.

21
     mMicrophoneMode = value.getResult().getMicrophoneMode();
22
     break;

El tercer caso tomará un resultado de audio y lo reproducirá para el usuario.

1
case AUDIO_OUT:
2
     if (mAudioOutSize <= value.getAudioOut().getSerializedSize()) {
3
         mAudioOutSize = value.getAudioOut().getSerializedSize();
4
     } else {
5
         mAudioOutSize = 0;
6
         onCompleted();
7
     }
8
     
9
     final ByteBuffer audioData =
10
             ByteBuffer.wrap(value.getAudioOut().getAudioData().toByteArray());
11
     mAudioTrack.write(audioData, audioData.remaining(),
12
             AudioTrack.WRITE_BLOCKING);
13
     mConversationCallback.onAudioSample(audioData);
14
     break;

El caso final simplemente devolverá errores que pudieran haber ocurrido durante el proceso de conversación.

1
case ERROR:
2
     mConversationCallback.onConversationError(value.getError());
3
     break;

Los dos métodos finales dentro de este flujo manejan estados de error y hacen la limpieza cuando se completa un resultado de la conversación.

1
@Override
2
 public void onError(Throwable t) {
3
     mConversationCallback.onError(t);
4
 }
5
 
6
 @Override
7
 public void onCompleted() {
8
     mConversationCallback.onResponseFinished();
9
     if (mMicrophoneMode == MicrophoneMode.DIALOG_FOLLOW_ON) {
10
         // Automatically start a new request

11
         startConversation();
12
     } else {
13
         // The conversation is done

14
         mConversationCallback.onConversationFinished();
15
     }
16
 }

Streaming de Audio

Luego, necesitarás crear un Runnable que manejará streaming de audio en un thread diferente.

1
private Runnable mStreamAssistantRequest = new Runnable() {
2
     @Override
3
     public void run() {
4
         ByteBuffer audioData = ByteBuffer.allocateDirect(AUDIO_RECORD_BLOCK_SIZE);
5
         int result = mAudioRecord.read(audioData, audioData.capacity(),
6
                 AudioRecord.READ_BLOCKING);
7
         if (result < 0) {
8
             return;
9
         }
10
         mRequestCallback.onAudioRecording();
11
         mAssistantRequestObserver.onNext(ConverseRequest.newBuilder()
12
                 .setAudioIn(ByteString.copyFrom(audioData))
13
                 .build());
14
         mAssistantHandler.post(mStreamAssistantRequest);
15
     }
16
 };

Creando el Asistente

Ahora que tus variables globales están definidas, es hora de crear el EmbeddedAssistant. Tendrás que obtener las credenciales para tu app utilizando la clase Credentials.java creada anteriormente.

1
public static UserCredentials generateCredentials(Context context, int resourceId)
2
         throws IOException, JSONException {
3
     return Credentials.fromResource(context, resourceId);
4
 }

Para que pueda instanciarse a sí misma, esta clase utiliza un constructor privado y el patrón builder.

1
private EmbeddedAssistant() {}
2
 
3
 public static class Builder {
4
     private EmbeddedAssistant mEmbeddedAssistant;
5
     private int mSampleRate;
6
 
7
     public Builder() {
8
         mEmbeddedAssistant = new EmbeddedAssistant();
9
     }

La clase interna Builder contiene múltiples métodos para inicializar los valores dentro de la clase EmbeddedAssistant, como frecuencia de muestreo, volumen, y credenciales de usuario. Una vez invocado el método build(), todos los valores definidos se configurarán en el EmbeddedAssistant, los objetos globales necesarios para operar se iniciarán, y se disparará un error si falta algún dato necesario.

1
    public Builder setRequestCallback(RequestCallback requestCallback) {
2
         mEmbeddedAssistant.mRequestCallback = requestCallback;
3
         return this;
4
     }
5
 
6
     public Builder setConversationCallback(ConversationCallback responseCallback) {
7
         mEmbeddedAssistant.mConversationCallback = responseCallback;
8
         return this;
9
     }
10
 
11
     public Builder setCredentials(UserCredentials userCredentials) {
12
         mEmbeddedAssistant.mUserCredentials = userCredentials;
13
         return this;
14
     }
15
 
16
     public Builder setAudioSampleRate(int sampleRate) {
17
         mSampleRate = sampleRate;
18
         return this;
19
     }
20
 
21
     public Builder setAudioVolume(int volume) {
22
         mEmbeddedAssistant.mVolume = volume;
23
         return this;
24
     }
25
 
26
     public EmbeddedAssistant build() {
27
         if (mEmbeddedAssistant.mRequestCallback == null) {
28
             throw new NullPointerException("There must be a defined RequestCallback");
29
         }
30
         if (mEmbeddedAssistant.mConversationCallback == null) {
31
             throw new NullPointerException("There must be a defined ConversationCallback");
32
         }
33
         if (mEmbeddedAssistant.mUserCredentials == null) {
34
             throw new NullPointerException("There must be provided credentials");
35
         }
36
         if (mSampleRate == 0) {
37
             throw new NullPointerException("There must be a defined sample rate");
38
         }
39
         final int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
40
 
41
         // Construct audio configurations.

42
         mEmbeddedAssistant.mAudioInConfig = AudioInConfig.newBuilder()
43
                 .setEncoding(AudioInConfig.Encoding.LINEAR16)
44
                 .setSampleRateHertz(mSampleRate)
45
                 .build();
46
         mEmbeddedAssistant.mAudioOutConfig = AudioOutConfig.newBuilder()
47
                 .setEncoding(AudioOutConfig.Encoding.LINEAR16)
48
                 .setSampleRateHertz(mSampleRate)
49
                 .setVolumePercentage(mEmbeddedAssistant.mVolume)
50
                 .build();
51
 
52
         // Construct AudioRecord & AudioTrack

53
         AudioFormat audioFormatOutputMono = new AudioFormat.Builder()
54
                 .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
55
                 .setEncoding(audioEncoding)
56
                 .setSampleRate(mSampleRate)
57
                 .build();
58
         int outputBufferSize = AudioTrack.getMinBufferSize(audioFormatOutputMono.getSampleRate(),
59
                 audioFormatOutputMono.getChannelMask(),
60
                 audioFormatOutputMono.getEncoding());
61
         mEmbeddedAssistant.mAudioTrack = new AudioTrack.Builder()
62
                 .setAudioFormat(audioFormatOutputMono)
63
                 .setBufferSizeInBytes(outputBufferSize)
64
                 .build();
65
         mEmbeddedAssistant.mAudioTrack.setVolume(mEmbeddedAssistant.mVolume *
66
                 AudioTrack.getMaxVolume() / 100.0f);
67
         mEmbeddedAssistant.mAudioTrack.play();
68
 
69
         AudioFormat audioFormatInputMono = new AudioFormat.Builder()
70
                 .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
71
                 .setEncoding(audioEncoding)
72
                 .setSampleRate(mSampleRate)
73
                 .build();
74
         int inputBufferSize = AudioRecord.getMinBufferSize(audioFormatInputMono.getSampleRate(),
75
                 audioFormatInputMono.getChannelMask(),
76
                 audioFormatInputMono.getEncoding());
77
         mEmbeddedAssistant.mAudioRecord = new AudioRecord.Builder()
78
                 .setAudioSource(AudioSource.VOICE_RECOGNITION)
79
                 .setAudioFormat(audioFormatInputMono)
80
                 .setBufferSizeInBytes(inputBufferSize)
81
                 .build();
82
 
83
         return mEmbeddedAssistant;
84
     }
85
 }
86
 

Conectándose a la API del Asistente

Luego de que el EmbeddedAssistant ha sido creado, debemos llamar al método connect() para poder conectarnos a la API del Asistente.

1
public void connect() {
2
     mAssistantThread = new HandlerThread("assistantThread");
3
     mAssistantThread.start();
4
     mAssistantHandler = new Handler(mAssistantThread.getLooper());
5
 
6
     ManagedChannel channel = ManagedChannelBuilder.forTarget(ASSISTANT_API_ENDPOINT).build();
7
     mAssistantService = EmbeddedAssistantGrpc.newStub(channel)
8
             .withCallCredentials(MoreCallCredentials.from(mUserCredentials));
9
 }

Luego de conectarte a la API, emplearás dos métodos para comenzar y detener conversaciones. Estos métodos enviarán objetos Runnable a mAssistantHandler, para pasar objetos de estado de la conversación hacia los flujos de petición y respuesta.

1
public void startConversation() {
2
     mAudioRecord.startRecording();
3
     mRequestCallback.onRequestStart();
4
     mAssistantHandler.post(new Runnable() {
5
         @Override
6
         public void run() {
7
             mAssistantRequestObserver = mAssistantService.converse(mAssistantResponseObserver);
8
             ConverseConfig.Builder converseConfigBuilder = ConverseConfig.newBuilder()
9
                     .setAudioInConfig(mAudioInConfig)
10
                     .setAudioOutConfig(mAudioOutConfig);
11
             if (mConversationState != null) {
12
                 converseConfigBuilder.setConverseState(ConverseState.newBuilder()
13
                         .setConversationState(mConversationState)
14
                         .build());
15
             }
16
             mAssistantRequestObserver.onNext(
17
                     ConverseRequest.newBuilder()
18
                             .setConfig(converseConfigBuilder.build())
19
                             .build());
20
         }
21
     });
22
     mAssistantHandler.post(mStreamAssistantRequest);
23
 }
24
 
25
 public void stopConversation() {
26
     mAssistantHandler.post(new Runnable() {
27
         @Override
28
         public void run() {
29
             mAssistantHandler.removeCallbacks(mStreamAssistantRequest);
30
             if (mAssistantRequestObserver != null) {
31
                 mAssistantRequestObserver.onCompleted();
32
                 mAssistantRequestObserver = null;
33
             }
34
         }
35
     });
36
 
37
     mAudioRecord.stop();
38
     mAudioTrack.play();
39
     mConversationCallback.onConversationFinished();
40
 }

Apagado

Finalmente el método destroy() será utilizado durante el apagado, cuando tu app se cierre y no requiera más acceso a la API del Asistente.

1
public void destroy() {
2
     mAssistantHandler.post(new Runnable() {
3
         @Override
4
         public void run() {
5
             mAssistantHandler.removeCallbacks(mStreamAssistantRequest);
6
         }
7
     });
8
     mAssistantThread.quitSafely();
9
     if (mAudioRecord != null) {
10
         mAudioRecord.stop();
11
         mAudioRecord = null;
12
     }
13
     if (mAudioTrack != null) {
14
         mAudioTrack.stop();
15
         mAudioTrack = null;
16
     }
17
 }

Utilizando el Asistente

Una vez desarrolladas tus clases auxiliares, es momento de utilizarlas. Lo harás editando tu clase MainActivity de Android Things para que interactúe con el EmbeddedAssistant y el hardware para controlar el Google Assistant. Primero, agrega la interfaz Button.OnButtonEventListener a tu Activity.

1
public class MainActivity extends Activity implements Button.OnButtonEventListener {

A continuación necesitarás agregar las variables miembro y las constantes necesarias para tu app. Estos valores controlarán el rebote del botón que dispara el Asistente, así como también el volumen, el formato de audio, la clase UserCredentials que creaste anteriormente, y el hardware de tu dispositivo.

1
private static final int BUTTON_DEBOUNCE_DELAY_MS = 20;
2
 private static final String PREF_CURRENT_VOLUME = "current_volume";
3
 private static final int SAMPLE_RATE = 16000;
4
 private static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT;
5
 private static final int DEFAULT_VOLUME = 100;
6
 
7
 private int initialVolume = DEFAULT_VOLUME;
8
 
9
 private static final AudioFormat AUDIO_FORMAT_STEREO =
10
         new AudioFormat.Builder()
11
                 .setChannelMask(AudioFormat.CHANNEL_IN_STEREO)
12
                 .setEncoding(ENCODING)
13
                 .setSampleRate(SAMPLE_RATE)
14
                 .build();
15
 
16
 // Hardware peripherals.

17
 private VoiceHat mVoiceHat;
18
 private Button mButton;
19
 private EmbeddedAssistant mEmbeddedAssistant;
20
 private UserCredentials userCredentials;

Una vez que tus constantes estén definidas, necesitarás crear algunos objetos callback que serán utilizados para las conversaciones y peticiones al asistente.

1
private ConversationCallback mConversationCallback = new ConversationCallback() {
2
     @Override
3
     public void onConversationEvent(EventType eventType) {}
4
 
5
     @Override
6
     public void onAudioSample(ByteBuffer audioSample) {}
7
 
8
     @Override
9
     public void onConversationError(Status error) {}
10
 
11
     @Override
12
     public void onError(Throwable throwable) {}
13
 
14
     @Override
15
     public void onVolumeChanged(int percentage) {
16
         SharedPreferences.Editor editor = PreferenceManager
17
                 .getDefaultSharedPreferences(AssistantActivity.this)
18
                 .edit();
19
         editor.putInt(PREF_CURRENT_VOLUME, percentage);
20
         editor.apply();
21
     }
22
 
23
     @Override
24
     public void onConversationFinished() {}
25
 };
26
 
27
 private RequestCallback mRequestCallback = new RequestCallback() {
28
     @Override
29
     public void onRequestStart() {
30
         //starting assistant request, enable microphones

31
     }
32
 
33
     @Override
34
     public void onSpeechRecognition(String utterance) {}
35
 };

En mConversationCallback, notarás que guardamos un porcentaje de variación del volumen en una preferencia compartida. Esto permite que el volumen del dispositivo permanezca constante para tus usuarios, inclusive después del reinicio.

Como el Asistente trabaja de manera asincrónica en tu dispositivo, inicializarás todo para usar la API del Asistente en el método onCreate(), invocando un conjunto de métodos auxiliares que definiremos en el resto de este tutorial.

1
@Override
2
 protected void onCreate(Bundle savedInstanceState) {
3
     super.onCreate(savedInstanceState);
4
 
5
     initVoiceHat();
6
     initButton();
7
     initVolume();
8
     initUserCredentials();
9
     initEmbeddedAssistant();
10
 }

El primer método auxiliar es initVoiceHat(). Si se conecta Voice Hat a un Raspberry Pi, este método iniciará el dispositivo para que los usuarios puedan utilizar el micrófono y el parlante integrados. Si no hay un Voice Hat conectado, entonces se pueden utilizar un parlante AUX estándar y un micrófono USB, y serán detectados automáticamente. Voice Hat utiliza I²S para manejar periféricos de audio en el bus, y está contenido en una clase driver escrita por Google.

1
private void initVoiceHat() {
2
     PeripheralManagerService pioService = new PeripheralManagerService();
3
     List<String> i2sDevices = pioService.getI2sDeviceList();
4
     if (i2sDevices.size() > 0) {
5
         try {
6
             mVoiceHat = new VoiceHat(
7
                     BoardDefaults.getI2SDeviceForVoiceHat(),
8
                     BoardDefaults.getGPIOForVoiceHatTrigger(),
9
                     AUDIO_FORMAT_STEREO
10
             );
11
             mVoiceHat.registerAudioInputDriver();
12
             mVoiceHat.registerAudioOutputDriver();
13
         } catch (IllegalStateException e) {}
14
     }
15
 }

En este ejemplo, el Asistente sólo responderá al mantener presionado un botón. Este botón se inicializa y configura de la siguiente manera:

1
private void initButton() {
2
     try {
3
         mButton = new Button(BoardDefaults.getGPIOForButton(),
4
                 Button.LogicState.PRESSED_WHEN_LOW);
5
         mButton.setDebounceDelay(BUTTON_DEBOUNCE_DELAY_MS);
6
         mButton.setOnButtonEventListener(this);
7
     } catch( IOException e ) {}
8
 }

Cuando el botón es presionado, el Asistente comenzará a escuchar, a la espera de una nueva conversación.

1
@Override
2
 public void onButtonEvent(Button button, boolean pressed) {
3
     if (pressed) {
4
         mEmbeddedAssistant.startConversation();
5
     }
6
 }

Puedes encontrar más información acerca de GPIO y Android Things en mi tutorial acerca de entrada y salida con Android Things.

Como hemos almacenado información en las SharedPreferences de nuestro dispositivo, podemos accederlas directamente para inicializar el volumen del dispositivo.

1
private void initVolume() {
2
     SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
3
     initialVolume = preferences.getInt(PREF_CURRENT_VOLUME, DEFAULT_VOLUME);
4
 }

El SDK del Asistente requiere autenticación para ser utilizado. Afortunadamente, hemos creado un método en la clase EmbeddedAssistant anteriormente en este tutorial, específicamente para esta situación.

1
private void initUserCredentials() {
2
     userCredentials = null;
3
     try {
4
         userCredentials = EmbeddedAssistant.generateCredentials(this, R.raw.credentials);
5
     } catch (IOException | JSONException e) {}
6
 }

El último método auxiliar invocado en onCreate() inicializará el objeto EmbeddedAssistant y lo conectará con la API.

1
private void initEmbeddedAssistant() {
2
     mEmbeddedAssistant = new EmbeddedAssistant.Builder()
3
             .setCredentials(userCredentials)
4
             .setAudioSampleRate(SAMPLE_RATE)
5
             .setAudioVolume(currentVolume)
6
             .setRequestCallback(mRequestCallback)
7
             .setConversationCallback(mConversationCallback)
8
             .build();
9
 
10
     mEmbeddedAssistant.connect();
11
 }

Por último, necesitarás apagar correctamente tus periféricos, actualizando el método onDestroy() de tu Activity.

1
@Override
2
 protected void onDestroy() {
3
     super.onDestroy();
4
     if (mButton != null) {
5
         try {
6
             mButton.close();
7
         } catch (IOException e) {}
8
 
9
         mButton = null;
10
     }
11
 
12
     if (mVoiceHat != null) {
13
         try {
14
             mVoiceHat.unregisterAudioOutputDriver();
15
             mVoiceHat.unregisterAudioInputDriver();
16
             mVoiceHat.close();
17
         } catch (IOException e) {}
18
         mVoiceHat = null;
19
     }
20
     mEmbeddedAssistant.destroy();
21
 }

¡Ahora, deberías ser capaz de interactuar con tu dispositivo Android Things como si fuera un Google Home!

Conclusión

En este tutorial, has aprendido acerca del Asistente de Google y cómo puede integrarse en tus aplicaciones Android Things. Esta función les da a tus usuarios una nueva manera de interactuar y controlar tu dispositivo, así como acceso a las diversas funcionalidades de Google disponibles. Esta es sólo una parte de las fantásticas funciones que pueden existir dentro de una app Android Things, y te permiten crear increíbles dispositivos nuevos para tus usuarios.

Mientras estés aquí, ¡revisa algunas de mis otras publicaciones acerca de Android Things en Envato Tuts+!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.