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.
Contents
- 0.1 Pré-requisitos
- 0.2 Seção 1: Noções básicas sobre a criação do aplicativo e a conexão com o Back4App
- 0.3 Seção 2: Salvando e recuperando objetos no Parse e exibindo no aplicativo
- 0.4 Seção 3: Ouvindo eventos em tempo real usando o Live Query e exibindo-os no aplicativo
- 1 Como interagir um aplicativo Android com um dispositivo IoT?
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
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.