Aplicación Android para Raspberry Pi

En nuestros tutoriales anteriores sobre IoT, cubrimos cómo configurar una Raspberry Pi y cómo conectarla a Parse Server utilizando Back4App API para guardar objetos en el servidor y realizar consultas y Live Queries.

Ahora cubrimos cómo reproducir todo lo hecho en el lado Raspberry al lado App. En este tutorial, describimos una aplicación Android para interactuar con el dispositivo IoT configurado anteriormente. Desde el lado del Parse Server, la App realiza las mismas tareas que la Raspberry: escribe objetos y realiza Queries y Live Queries. Ten en cuenta que estas funcionalidades pueden ser útiles incluso si no planeas desarrollar una aplicación IoT.

Parse ha demostrado ser un marco increíble para crear aplicaciones IoT. En 2020, con la adición del protocolo GraphQL API proporciona una forma aún mejor de recuperar datos.

Proporcionamos nuestros códigos como un primer paso para que desarrolles tus aplicaciones deseadas.

Requisitos previos

El único prerrequisito es completar nuestro tutorial Android QuickStart. Para ello, es necesario hacer clic en el enlace que se menciona a continuación.

https://www.back4app.com/docs/pages/android/how-to-build-an-android-app-on-back4app

Aquí hay un tutorial sobre LiveQuery que será necesario en la sección 3 de este tutorial IoT Series. Sin embargo, no es necesario completarlo antes.

https://docs.back4app.com/docs/android/live-query/

Si tienes alguna duda a lo largo del tutorial, puedes resolverla siguiendo la guía oficial de Parse para Android en el siguiente enlace.

http://docs.parseplatform.org/android/guide/

Sección 1: Conceptos básicos para crear tu App y conectarte con Back4App

En este proyecto, nombramos a nuestra clase como “MainActivity.java”. Aquí está el código básico que le ayudará a empezar.

public class MainActivity extends AppCompatActivity {
   @Override
   protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
     // Insertar barra de herramientas en la app. Más descripción de esta barra en el archivo menu_main_activity.xml 
      Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
      setSupportActionBar(toolbar); 
   }
   @Override
   public boolean onCreateOptionsMenu(Menu menu) {
     // Infla el menú; esto añade elementos a la barra de acciones si está presente. 
      getMenuInflater().inflate(R.menu.menu_main_activity, menu);
     return true;
   }
   @Override
   public boolean onOptionsItemSelected(MenuItem item) {
     // Aquí se gestionan los clics en la barra de acciones. La barra de acción
      // manejará automáticamente los clics en el botón Home/Up, siempre y cuando
      // que especifiques una actividad padre en AndroidManifest.xml. 
     int id = item.getItemId();
     //noinspección SimplifiableIfStatement 
     if (id == R.id.action_settings) {
        return true;
      }
      return super.onOptionsItemSelected(item);
   }
}

Puede que necesites añadir las siguientes importaciones:

import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.EditText;
import android.widget.Button;
import android.widget.ToggleButton;
import android.view.View.OnClickListener;

En tu archivo XML de diseño, asegúrate de añadir el siguiente código:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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:id="@+id/miCoordinatorLayout"
   tools:context="com.example.guilherme.myiotapplication.MainActivity">
   <includelayout="@layout/content_main_activity" />
   <android.support.design.widget.AppBarLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:theme="@style/AppTheme.AppBarOverlay">
      <android.support.v7.widget.Toolbar
          android:id="@+id/toolbar"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:background="?attr/colorPrimary"
          app:popupTheme="@style/AppTheme.PopupOverlay" />
   </android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>

Para conectar tu app a Back4App, añade el siguiente código dentro de la función onCreate() de tu clase.

// Inicializando Parse Server
Parse.initialize(new Parse.Configuration.Builder(this)
    .applicationId("YOUR_APP_ID") // desde Core Settings, en "Features" .
    .clientKey("YOUR_CLIENT_KEY") // de Core Settings, en "Features" .
    .server("https://parseapi.back4app.com/")
    .build()
);

Recuerda seguir paso a paso las instrucciones de QuickStart para conceder acceso a Internet a tu aplicación.

Sección 2: Guardar y recuperar objetos en Parse y mostrarlos en la aplicación

Comienza añadiendo las siguientes importaciones

import com.parse.FindCallback;
import com.parse.Parse;
import com.parse.ParseException;
import com.parse.ParseObject;
import com.parse.ParseQuery;
import com.parse.SaveCallback;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.List;

En esta sección, guardamos objetos de la clase “CommandGPIO1” con un clic de botón. Cada Parse Object tiene tres atributos por defecto: objectId, createdAt , y updatedAt. Nuestra clase tendrá dos atributos adicionales: contenido y destino. Los valores para el contenido serán “on” o “off”, y representan un estado que se enviará a un LED. Para el destino, todos los objetos contendrán “command”, pero podríamos definir diferentes cadenas para diferentes LEDs.

Por ejemplo:

Creamos dos botones, y cada uno de ellos escribe un valor diferente en el contenido. Para crear un botón, añadir el siguiente código a su archivo XML de diseño:

<Botón
   android:id="@+id/buttonSendOn"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_gravity="top|start"
   android:layout_marginBottom="0dp"
   android:layout_marginTop="120dp"
   android:layout_marginEnd="0dp"
   android:layout_marginRight="0dp"
   android:layout_marginLeft="16dp"
   android:layout_marginStart="16dp"
   android:text="Enviar ON"/>

En su archivo de clase, la creación del botón es tan fácil como escribir:

Button buttonOn = (Button) findViewById(R.id.buttonSendOn);

Si eres un principiante en la programación Android, ten en cuenta que el argumento “findViewById” contiene “buttonSendOn”, y este es el Android:Id que definimos en el XML.

Queremos que este botón guarde un objeto en Parse Server cuando sea pulsado. Para ello, añade el siguiente código:

buttonOn.setOnClickListener( new OnClickListener(){
   @Override
  public void onClick(final View view) {
     // Crear nuevo objeto y asignarle los atributos adecuados 
      ParseObject command = new ParseObject("ComandoGPIO1");
      command.put("contenido","on");
      command.put("destino", "comando");
      command.saveInBackground(new SaveCallback(){
      @Override
     public void done(ParseException e){
         Snackbar.make(view, "Enviado ON a Salida", Snackbar.LENGTH_LONG )
         .setAction("Acción", null).show();
      }
   }); 
});

Nota, podemos añadir cualquier función que queramos que se ejecute dentro de la función callback onClick. Aquí, creamos un objeto de clase “CommandGPIO1”, establecemos el contenido como “on”, y el destino como “command”.

Es útil saber que NO tenemos que definir esta clase de antemano en el panel de Parse. Si más tarde decides añadir un nuevo atributo, o cambiar el nombre de tu clase, entonces sólo tienes que trabajar en tu código, y los cambios se actualizarán automáticamente en el panel de control.

En este punto, es mejor que pruebes tu aplicación y compruebes si el objeto se está creando.

El SaveCallback muestra un snackbar, que es una retroalimentación ligera en la parte inferior de tu pantalla, como se muestra en la figura de arriba.

Copia este código para crear un botón que escriba objetos con “off”. Cambia las siguientes líneas en el layout XML

android:id="@+id/buttonSendOff"
android:layout_gravity="top|center"
android:layout_marginLeft="0dp"
android:layout_marginStart="0dp"
android:text="Enviar OFF"/>

Crear un nuevo botón y cambiar la línea donde se está escribiendo el contenido:

Button buttonOff = (Button) findViewById(R.id.buttonSendOff);
command.put("contenido","apagado");

Ahora que las cosas funcionan correctamente en Parse Server, queremos proporcionar una respuesta permanente al usuario de la aplicación. Para ello, vamos a utilizar el elemento EditText, como se muestra a continuación.

Define este elemento en el layout XML:

<EditarTexto
    android:id="@+id/status1Text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="top|start"
    android:maxLíneas ="2"
    android:ems="10"
    android:singleLine="false"
    app:layout_behavior="@string/appbar_scrolling_view_behavior" />

Dentro del ámbito onCreate() de tu clase, añade el siguiente código:

// Definir elemento de texto para mostrar el estado del pin de salida, que recibe comandos de la app.
// Los comandos son enviados por los botones, que se describen más adelante en este código.
final EditText textOutPin = (EditText) findViewById(R.id.status1Text);
textOutPin.setFocusable(false);
textOutPin.setClickable(true);
textOutPin.setText("Cargando estado del pin de salida...");

La primera línea crea el objeto. La segunda línea hace que no sea editable para los usuarios. La tercera línea lo hace clicable, para que pueda ser copiado y pegado. La cuarta línea establece el texto inicial.

Queremos escribir el campo de contenido del último objeto “CommandGPIO1” guardado en Parse en este objeto EditText. Aunque podríamos hacer esto usando alguna lógica dentro del código, en realidad recuperaremos objetos de Parse realizando una ParseQuery ya que esto proporciona resultados más realistas y robustos.

El siguiente código declara, establece parámetros e imprime los resultados de un Parse Query.

ParseQuery<ParseObject> queryOut = ParseQuery.getQuery("ComandoGPIO1");
queryOut.whereEqualTo("destino", "comando");
queryOut.addDescendingOrder("createdAt");
queryOut.setLimit(1);
queryOut.findInBackground(new FindCallback<ParseObject>() {
   @Override
  public void done(List<ParseObject> objects, ParseException e) {
     if (e == null){
         textOutPin.setText("La salida es " + objects.get(0).getString("content"));
      }
     else{
         textOutPin.setText("Error: " + e.getMessage());
      }
   }
});

La primera línea crea la consulta. La segunda añade una restricción para seleccionar sólo aquellos objetos cuyo campo de destino contenga “comando”. La tercera línea los objetos empezando por los más nuevos. La cuarta línea limita los resultados a uno, asegurando que recuperaremos el objeto más nuevo.

La quinta línea activa la consulta, y llama a la función callback cuando termina. Aquí, escribimos el contenido del objeto recuperado en el objeto EditText previamente definido.

Añadiremos ese trozo de código justo después de declarar el objeto EditText, para que se realice una consulta cuando se abra la aplicación, y también en el SaveCallback cuando se guarde un nuevo objeto en Parse, para actualizar el texto automáticamente cuando se creen nuevos objetos.

Llegados a este punto, tu app debería funcionar como se muestra en las siguientes capturas de pantalla.

Por último, añadimos un botón de actualización para que los usuarios puedan realizar la consulta anterior siempre que lo deseen. Se hará con un estilo diferente de botón, es decir, un Botón de Acción Flotante.

Añade este código en el archivo XML de diseño:

<android.support.design.widget.FloatingActionButton
    android:id="@+id/fab"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="top|end"
    android:layout_marginBottom="0dp"
    android:layout_marginTop="120dp"
    android:layout_marginEnd="16dp"
    android:layout_marginRight="16dp"
    android:layout_marginLeft="0dp"
    android:layout_marginStart="0dp"
    app:srcCompat="@android:drawable/ic_popup_sync" />

Ahora, dentro del ámbito onCreate() de la clase, añade el siguiente código:

// Botón Refresh para obtener el estado de la salida cuando se solicite.
// Aquí se realiza la misma consulta que la primera
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener(){
    @Override
   public void onClick(final View view){
        ParseQuery<ParseObject> queryOut = ParseQuery.getQuery("CommandGPIO1");
        queryOut.whereEqualTo("destino", "comando");
        queryOut.addDescendingOrder("createdAt");
        queryOut.setLimit(1);
        queryOut.findInBackground(new FindCallback<ParseObject>() {
            @Override
           public void done(List<ParseObject> objects, ParseException e) {
              if (e == null){
                   textOutPin.setText("La salida es " + objects.get(0).getString("content"));
               }
              else{
                   textOutPin.setText("Error: " + e.getMessage());
               }
               Snackbar.make(view, "Estado actualizado de Salida", Snackbar.LENGTH_LONG ).setAction("Acción", null).show();
            }
        });
     }
 });

En este punto ya podemos guardar objetos en Parse Server, recuperar información de ellos y mostrarlos en tu aplicación. Tu aplicación debería estar funcionando como se muestra en la siguiente figura:

Sección 3: Escuchando Eventos en Tiempo Real Usando Live Query y Mostrándolos en la Aplicación

En esta sección, monitorizamos en tiempo real la clase “InputGPIO” en el Parse Dashboard, y mostramos el “contenido” de los objetos para el usuario.

Comienza definiendo un nuevo EditText en el XML del layout:

<EditText
    android:id="@+id/status2Text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center|start"
    android:layout_marginBottom="0dp"
    android:maxLíneas ="2"
    android:ems="10"
    android:singleLine="false"
    app:layout_behavior="@string/appbar_scrolling_view_behavior" />

En el ámbito de onCreate(), crear un nuevo objeto EditText y realizar una consulta de inicialización (no en vivo todavía) inmediatamente después.

final EditText textInPin = (EditText) findViewById(R.id.status2Text);
textInPin.setFocusable(false);
textInPin.setClickable(true);
textInPin.setText("Cargando estado del pin de entrada...");
// Consulta inicial (no en vivo) para obtener el último estado almacenado del pin
ParseQuery<ParseObject> queryIn = ParseQuery.getQuery("InputGPIO");
queryIn.whereEqualTo("tipo", "interrupción");
queryIn.addDescendingOrder("createdAt");
queryIn.setLimit(1);
queryIn.findInBackground(new FindCallback<ParseObject>() {
    @Override
   public void done(List<ParseObject> objects, ParseException e) {
       if (e == null){
            textInPin.setText("La entrada es " + objects.get(0).getString("content"));
        }
       else{
            textInPin.setText("Error: " + e.getMessage());
        }
    }
});

Observa que los atributos de “InputGPIO” son content y type, este último juega el papel de destino en la clase “CommandGPIO”.

Llegados a este punto, tu aplicación debería parecerse a la figura que se muestra a continuación.

Ahora implementaremos la consulta en vivo. El tutorial al que nos referimos anteriormente es necesario aquí.

https://docs.back4app.com/docs/android/live-query/

Asegúrate de seguir el paso 1 de nuestro tutorial Live Query para seleccionar en qué clases se habilitará Live Query y definir un nombre de subdominio. Puede que necesite crear la clase “InputGPIO” (si no existe ya) para habilitar la función en ella. Sigue el paso 2 del tutorial para configurar el cliente Live Query en Android.

Recuerda añadir las siguientes importaciones:

import tgio.parselivequery.BaseQuery;
importar tgio.parselivequery.LiveQueryClient;
import tgio.parselivequery.LiveQueryEvent;
importar tgio.parselivequery.Subscription;
importar tgio.parselivequery.interfaces.OnListener;

Añade el siguiente código para inicializar LiveQuery y definir sus parámetros.

// Inicialización de LiveQuery
LiveQueryClient.init("wss:TU_NOMBRE_SUBDOMINIO.back4app.io", "TU_ID_APP", true );
LiveQueryClient.connect();
// Definición de atributos de LiveQuery 
 Subscription subIn = new BaseQuery.Builder("InputGPIO")
    .where("tipo","interrupción")
    .addField("contenido")
    .build()
    .subscribe();

Al definir la suscripción, tomamos sólo aquellos elementos cuyo atributo type sea “interrupt” y recuperamos el contenido del campo.

Ahora, añade el siguiente código para responder cada vez que se cree un objeto definido por la suscripción en Parse Server.

// Comenzando a escuchar los eventos LiveQuery CREATE, obteniendo su contenido y escribiendo
subIn.on(LiveQueryEvent.CREATE, new OnListener() {
    @Override
   public void on(objeto JSONObject) {
       try {
           final String subInContent = (String) ((JSONObject) object.get("object")).get("content");
            runOnUiThread(new Runnable() {
                @Override
               public void run() {
                   textInPin.setText("La entrada es " + subInContent);
                   Snackbar.make(findViewById(R.id.myCoordinatorLayout), " El pin de entrada ha cambiado a " + subInContent.toUpperCase(), Snackbar.LENGTH_LONG ).setAction("Acción", null).show();
                }
            });
        } catch (JSONException e){
            e.printStackTrace();
        }
    }
});

El campo de contenido del objeto se mostrará en nuestro nuevo elemento EditText.

Lo que hemos hecho hasta este punto es suficiente para mostrar cualquier entrada enviada por el dispositivo IoT. Sin embargo, para verificar cómo está funcionando la aplicación, implementaremos otro tipo de botón, es decir, un Toggle Button que creará un objeto de clase “InputGPIO” y lo guardará en Parse Server.

Añade el siguiente código al fichero XML del layout:

<ToggleButton
    android:id="@+id/toggleTest"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center|end"
    android:layout_marginBottom="0dp"
    android:layout_marginTop="100dp"
    android:layout_marginEnd="0dp"
    android:layout_marginRight="0dp"
    android:layout_marginLeft="0dp"
    android:layout_marginStart="0dp"
    android:textOn = "entrada activada"
    android:textOff = "input off"/>

Agregue el código mencionado a continuación en el ámbito de la función onCreate(), en la clase MainActivity.

// El botón Toggle está aquí sólo para emular los objetos que el hardware crearía
ToggleButton toggleTest = (ToggleButton) findViewById(R.id.toggleTest);
toggleTest.setOnClickListener( new OnClickListener(){
    @Override
   public void onClick(final View view) {
        ParseObject comando = new ParseObject("InputGPIO");
       if(gpioStatusTest.equals("off"))
           gpioStatusTest = "on";
       si no
            gpioStatusTest = "off";
        command.put("tipo","interrupción");
        command.put("content", "Desde Toggle: " + gpioStatusTest);
        command.saveInBackground(new SaveCallback(){
            @Override
           public void done(ParseException e){
                Snackbar.make(view, "Changed input pin", Snackbar.LENGTH_LONG ).setAction("Action", null).show();
            }
        });
    }
});

Además, declara el String en el ámbito de MainActivity.

String gpioStatusTest = "off";

¡Hemos terminado con la aplicación! Al final de este tutorial, tu aplicación debería parecerse bastante a la figura que se muestra a continuación.

Si quieres continuar desarrollando nuestra aplicación IoT, por favor lee nuestra Serie IoT, una guía paso a paso, que te enseña lo básico para configurar una Raspberry Pi y aplicaciones específicas, como recuperar y guardar objetos desde Parse Server usando JavaScript.

Nuestros códigos están disponibles en el siguiente enlace:

https://github.com/back4app/iot-raspberry-node

Referencias

Conectando Raspberry Pi a Parse Server

Configuración de Raspberry Pi

¿Cómo interactuar una aplicación Android con un dispositivo IoT?

Sección 1: Fundamentos para crear tu aplicación y conectarla con el servidor.
Sección 2: Guardar y recuperar objetos al Parse y mostrarlos en la aplicación.
Sección 3: Escuchar eventos en tiempo real mediante consultas en vivo y mostrarlos en la aplicación.


Leave a reply

Your email address will not be published.