Aplicativo Android para Raspberry Pi

Em nossos tutoriais anteriores sobre IoT, abordamos como configurar um Raspberry Pi e como conectá-lo ao Parse Server usando a API Back4App para salvar objetos no servidor e realizar consultas e consultas ao vivo.

Agora, abordaremos como reproduzir tudo o que foi feito no lado do Raspberry para o lado do aplicativo. Neste tutorial, descrevemos um aplicativo Android para interagir com o dispositivo IoT configurado anteriormente. No lado do Parse Server, o aplicativo executa as mesmas tarefas que o Raspberry: grava objetos e realiza consultas e consultas ao vivo. Observe que essas funcionalidades podem ser úteis mesmo que você não planeje desenvolver um aplicativo de IoT!

O Parse provou ser uma estrutura incrível para criar aplicativos de IoT. Em 2020, com a adição do protocolo GraphQL API, ele oferece uma maneira ainda melhor de recuperar dados.

Fornecemos nossos códigos como uma primeira etapa para que você desenvolva os aplicativos desejados.

Pré-requisitos

O único pré-requisito é concluir nosso tutorial Android QuickStart. Para isso, você precisa clicar no link mencionado abaixo.

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

Aqui está um tutorial sobre o LiveQuery que será necessário na seção 3 deste tutorial da série IoT. No entanto, não é necessário concluí-lo antes.

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

Se tiver alguma dúvida durante o tutorial, você poderá resolvê-la seguindo o guia oficial do Parse para Android no link abaixo.

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

Seção 1: Noções básicas sobre a criação do aplicativo e a conexão com o Back4App

Neste projeto, nomeamos nossa classe como “MainActivity.java”. Aqui está o código básico que o ajudará a começar.

public class MainActivity extends AppCompatActivity {
   @Override
   protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
     // Insira a barra de ferramentas no aplicativo. Mais descrição dessa barra no arquivo menu_main_activity.xml 
      Barra de ferramentas barra de ferramentas = (Barra de ferramentas) findViewById(R.id.toolbar);
      setSupportActionBar(toolbar); 
   }
   @Override
   public boolean onCreateOptionsMenu(Menu menu) {
     // Inflaciona o menu; isso adiciona itens à barra de ação, se ela estiver presente. 
      getMenuInflater().inflate(R.menu.menu_main_activity, menu);
     return true;
   }
   @Override
   public boolean onOptionsItemSelected(MenuItem item) {
     // Aqui são tratados os cliques no item da barra de ação. A barra de ação tratará
      // automaticamente os cliques no botão Home/Up, desde que
      // desde que você especifique uma atividade pai no AndroidManifest.xml. 
     int id = item.getItemId();
     //sem inspeção SimplifiableIfStatement 
     se (id == R.id.action_settings) {
        return true;
      }
      return super.onOptionsItemSelected(item);
   }
}

Talvez você precise adicionar as seguintes importações:

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

Em seu arquivo XML de layout, certifique-se de adicionar o seguinte 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/myCoordinatorLayout"
   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" (envolver conteúdo)
      android:theme="@style/AppTheme.AppBarOverlay">
      <android.support.v7.widget.Toolbar
          android:id="@+id/toolbar"
          android:layout_width="match_parent"
          android:layout_height="wrap_content" (envolver conteúdo)
          android:background="?attr/colorPrimary"
          app:popupTheme="@style/AppTheme.PopupOverlay" />
   </android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>

Para conectar seu aplicativo ao Back4App, adicione o seguinte código na função onCreate() de sua classe.

// Inicialização do servidor Parse
Parse.initialize(new Parse.Configuration.Builder(this)
    .applicationId("YOUR_APP_ID") // nas configurações principais, em "Features" (Recursos) 
    .clientKey("YOUR_CLIENT_KEY") // das Core Settings, em "Features" 
    .server("https://parseapi.back4app.com/")
    .build()
);

Lembre-se de seguir as instruções passo a passo no QuickStart para conceder acesso à Internet ao seu aplicativo.

Seção 2: Salvando e recuperando objetos no Parse e exibindo no aplicativo

Comece adicionando as importações abaixo

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;

Nesta seção, salvamos objetos da classe “CommandGPIO1” com um clique no botão. Todo Parse Object tem três atributos padrão: objectId, createdAt e updatedAt. Nossa classe terá mais dois atributos adicionais: content e destination. Os valores de conteúdo serão “on” ou “off” e representam um status a ser enviado a um LED. Para o destino, todos os objetos conterão “command”, mas podemos definir cadeias de caracteres diferentes para LEDs diferentes.

Por exemplo:

Criamos dois botões, e cada um deles grava um valor diferente no conteúdo. Para criar um botão, adicione o seguinte código ao seu arquivo XML de layout:

<Button
   android:id="@+id/buttonSendOn"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content" (envolver conteúdo)
   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="Send ON"/>

Em seu arquivo de classe, criar o botão é tão fácil quanto escrever:

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

Se você for um iniciante em programação para Android, observe que o argumento “findViewById” contém “buttonSendOn”, e esse é o Android:Id que definimos no XML.

Queremos que esse botão salve um objeto no Parse Server quando for clicado. Para isso, adicione o seguinte código:

buttonOn.setOnClickListener( new OnClickListener(){
   @Override
  public void onClick(final View view) {
     // Criação de um novo objeto e atribuição dos atributos adequados 
      ParseObject command = new ParseObject("CommandGPIO1");
      command.put("content","on");
      command.put("destination", "command");
      command.saveInBackground(new SaveCallback(){
      @Override
     public void done(ParseException e){
         Snackbar.make(view, "Sent ON to Output", Snackbar.LENGTH_LONG )
         .setAction("Action", null).show();
      }
   }); 
});

Observe que podemos adicionar qualquer função que quisermos executar na função de retorno de chamada onClick. Aqui, criamos um objeto da classe “CommandGPIO1”, definimos o conteúdo como “on” e o destino como “command”.

É útil saber que NÃO é necessário definir essa classe previamente no painel do Parse! Se mais tarde você decidir adicionar um novo atributo ou alterar o nome da classe, basta trabalhar no código, e as alterações serão atualizadas automaticamente no painel.

Neste ponto, é melhor testar seu aplicativo e verificar se o objeto está sendo criado!

O SaveCallback mostra uma barra de lanches, que é um feedback leve na parte inferior da tela, conforme mostrado na figura acima.

Copie esse código para criar um botão que grava objetos com “off”. Altere as seguintes linhas no XML do layout

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

Crie um novo botão e altere a linha em que o conteúdo está sendo escrito:

Botão buttonOff = (Botão) findViewById(R.id.buttonSendOff);
command.put("content","off");

Agora que tudo está funcionando corretamente no Parse Server, queremos fornecer um feedback permanente ao usuário do aplicativo. Para isso, usaremos o elemento EditText, conforme mostrado abaixo.

Defina esse elemento no XML do layout:

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

No escopo onCreate() de sua classe, adicione o seguinte código:

// Definindo o elemento de texto para mostrar o status do pino de saída, que recebe comandos do aplicativo
// Os comandos são enviados por botões, que serão descritos posteriormente neste código.
final EditText textOutPin = (EditText) findViewById(R.id.status1Text);
textOutPin.setFocusable(false);
textOutPin.setClickable(true);
textOutPin.setText("Carregando o status do pino de saída...");

A primeira linha cria o objeto. A segunda linha o torna não editável para os usuários. A terceira linha o torna clicável, para que possa ser copiado e colado. A quarta linha define o texto inicial.

Queremos escrever o campo de conteúdo do último objeto “CommandGPIO1” salvo no Parse nesse objeto EditText. Embora pudéssemos fazer isso usando alguma lógica dentro do código, na verdade, recuperaremos objetos do Parse executando uma ParseQuery, pois isso fornece resultados mais realistas e robustos.

O código abaixo declara, define parâmetros e imprime os resultados de uma Parse Query.

ParseQuery<ParseObject> queryOut = ParseQuery.getQuery("CommandGPIO1");
queryOut.whereEqualTo("destination", "command");
queryOut.addDescendingOrder("createdAt");
queryOut.setLimit(1);
queryOut.findInBackground(new FindCallback<ParseObject>() {
   @Override
  public void done(List<ParseObject> objects, ParseException e) {
     se (e == null){
         textOutPin.setText("Output is " + objects.get(0).getString("content"));
      }
     else{
         textOutPin.setText("Error: " + e.getMessage());
      }
   }
});

A primeira linha cria a consulta. A segunda adiciona uma restrição para selecionar somente os objetos cujo campo de destino contenha “command”. A terceira linha seleciona os objetos começando pelos mais novos. A quarta linha limita os resultados a um, garantindo que recuperaremos o objeto mais novo.

A quinta linha ativa a consulta e chama a função de retorno de chamada quando ela é concluída. Aqui, escrevemos o conteúdo do objeto recuperado no objeto EditText definido anteriormente.

Adicionaremos esse trecho de código logo após declarar o objeto EditText, para que uma consulta seja executada quando o aplicativo for aberto e também no SaveCallback ao salvar um novo objeto no Parse, para atualizar o texto automaticamente ao criar novos objetos.

Nesse ponto, seu aplicativo deve funcionar conforme ilustrado nas capturas de tela a seguir.

Por fim, adicionamos um botão de atualização para permitir que os usuários realizem a consulta acima sempre que desejarem. Isso será feito com um estilo diferente de botão, ou seja, um botão de ação flutuante.

Adicione este código ao arquivo XML do layout:

<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" />

Agora, no escopo onCreate() da classe, adicione o seguinte código:

// Botão Atualizar para obter o status da saída quando solicitado.
// A mesma consulta que a primeira é realizada aqui
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("destination", "command");
        queryOut.addDescendingOrder("createdAt");
        queryOut.setLimit(1);
        queryOut.findInBackground(new FindCallback<ParseObject>() {
            @Override
           public void done(List<ParseObject> objects, ParseException e) {
              se (e == null){
                   textOutPin.setText("Output is " + objects.get(0).getString("content"));
               }
              else{
                   textOutPin.setText("Error: " + e.getMessage());
               }
               Snackbar.make(view, "Updated status of Output", Snackbar.LENGTH_LONG ).setAction("Action", null).show();
            }
        });
     }
 });

Neste ponto, agora podemos salvar objetos no Parse Server, recuperar informações deles e exibi-los em seu aplicativo! Seu aplicativo deve estar funcionando como mostrado na figura abaixo:

Seção 3: Ouvindo eventos em tempo real usando o Live Query e exibindo-os no aplicativo

Nesta seção, monitoramos em tempo real a classe “InputGPIO” no Parse Dashboard e exibimos o “conteúdo” dos objetos para o usuário.

Comece definindo um novo EditText no XML do 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:maxLines ="2"
    android:ems="10"
    android:singleLine="false"
    app:layout_behavior="@string/appbar_scrolling_view_behavior" />

No escopo de onCreate(), crie um novo objeto EditText e execute uma consulta de inicialização (ainda não ativa) imediatamente depois.

final EditText textInPin = (EditText) findViewById(R.id.status2Text);
textInPin.setFocusable(false);
textInPin.setClickable(true);
textInPin.setText("Carregando o status do pino de entrada...");
// Consulta inicial (não ativa) para obter o último status armazenado do pino
ParseQuery<ParseObject> queryIn = ParseQuery.getQuery("InputGPIO");
queryIn.whereEqualTo("type", "interrupt");
queryIn.addDescendingOrder("createdAt");
queryIn.setLimit(1);
queryIn.findInBackground(new FindCallback<ParseObject>() {
    @Override
   public void done(List<ParseObject> objects, ParseException e) {
       se (e == null){
            textInPin.setText("Input is " + objects.get(0).getString("content"));
        }
       else{
            textInPin.setText("Error: " + e.getMessage());
        }
    }
});

Observe que os atributos de “InputGPIO” são content e type, sendo que o último desempenha o papel de destino na classe “CommandGPIO”.

Neste ponto, seu aplicativo deve estar parecido com a figura mostrada abaixo.

Agora vamos implementar o Live Query. O tutorial mencionado anteriormente é necessário aqui.

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

Certifique-se de seguir a etapa 1 do nosso tutorial Live Query para selecionar em quais classes o Live Query será ativado e definir um nome de subdomínio. Talvez você precise criar a classe “InputGPIO” (se ela ainda não existir) para ativar o recurso nela. Siga a etapa 2 do tutorial para configurar o cliente do Live Query no Android.

Lembre-se de adicionar as seguintes importações:

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

Adicione o seguinte código para inicializar o LiveQuery e definir seus parâmetros.

// Inicialização do Live Query
LiveQueryClient.init("wss:YOUR_SUBDOMAIN_NAME.back4app.io", "YOUR_APP_ID", true );
LiveQueryClient.connect();
// Definição de atributos do LiveQuery 
 Assinatura subIn = novo BaseQuery.Builder("InputGPIO")
    .where("type","interrupt")
    .addField("content")
    .build()
    .subscribe();

Ao definir a assinatura, pegamos apenas os elementos cujo atributo type é “interrupt” e recuperamos o conteúdo do campo.

Agora, adicione o seguinte código para responder sempre que um objeto definido pela assinatura for criado no Parse Server.

// Começando a ouvir os eventos CREATE do LiveQuery, obtendo seu conteúdo e escrevendo
subIn.on(LiveQueryEvent.CREATE, new OnListener() {
    @Override
   public void on(JSONObject object) {
       try {
           final String subInContent = (String) ((JSONObject) object.get("object")).get("content");
            runOnUiThread(new Runnable() {
                @Override
               public void run() {
                   textInPin.setText("A entrada é " + subInContent);
                   Snackbar.make(findViewById(R.id.myCoordinatorLayout), "Input pin was changed to " + subInContent.toUpperCase(), Snackbar.LENGTH_LONG ).setAction("Action", null).show();
                }
            });
        } catch (JSONException e){
            e.printStackTrace();
        }
    }
});

O campo de conteúdo do objeto será exibido em nosso novo elemento EditText.

O que fizemos até este ponto é suficiente para exibir qualquer entrada enviada pelo dispositivo IoT. Entretanto, para verificar como o aplicativo está funcionando, implementaremos outro tipo de botão, ou seja, um botão de alternância que criará um objeto de classe “InputGPIO” e o salvará no Parse Server.

Adicione o seguinte código ao arquivo XML do layout:

<ToggleButton
    android:id="@+id/toggleTest"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" (envolver conteúdo)
    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 ligada"
    android:textOff = "input off"/>

Adicione o código mencionado abaixo no escopo da função onCreate(), na classe MainActivity.

// O botão de alternância está aqui apenas para emular os objetos que o hardware criaria
ToggleButton toggleTest = (ToggleButton) findViewById(R.id.toggleTest);
toggleTest.setOnClickListener( new OnClickListener(){
    @Override
   public void onClick(final View view) {
        ParseObject command = new ParseObject("InputGPIO");
       Se(gpioStatusTest.equals("off"))
           gpioStatusTest = "on";
       senão
            gpioStatusTest = "off";
        command.put("type","interrupt");
        command.put("content", "From 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();
            }
        });
    }
});

Além disso, declare o String no escopo da MainActivity.

String gpioStatusTest = "off";

Terminamos o aplicativo! Ao final deste tutorial, seu aplicativo deverá ser bastante semelhante à figura mostrada abaixo.

Se quiser continuar a desenvolver nosso aplicativo de IoT, leia nossa série IoT, um guia passo a passo que ensina os conceitos básicos para configurar um Raspberry Pi e aplicativos específicos, como recuperar e salvar objetos do Parse Server usando JavaScript.

Nossos códigos estão disponíveis no seguinte link:

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

Referências

Conectando o Raspberry Pi ao Parse Server

Configuração do Raspberry Pi

Como interagir um aplicativo Android com um dispositivo IoT?

Seção 1: Noções básicas sobre a criação do seu aplicativo e conexão com o servidor.
Seção 2: Salvando e recuperando objetos na Parse e exibindo no aplicativo.
Seção 3: Ouvindo eventos em tempo real usando consulta ao vivo e exibindo no aplicativo.


Leave a reply

Your email address will not be published.