App Android per Raspberry Pi

Nelle nostre precedenti esercitazioni sull’IoT, abbiamo illustrato come configurare un Raspberry Pi e come collegarlo al server Parse utilizzando l’ API Back4App per salvare gli oggetti sul server ed eseguire query e query live.

Ora ci occupiamo di come riprodurre tutto ciò che è stato fatto sul lato Raspberry sul lato App. In questa esercitazione, descriviamo un’applicazione Android per interagire con il dispositivo IoT configurato in precedenza. Dal lato del server Parse, l’applicazione esegue le stesse operazioni del Raspberry: scrive oggetti ed esegue query e query live. Si noti che queste funzionalità possono essere utili anche se non si intende sviluppare un’applicazione IoT!

Parse ha dimostrato di essere un framework straordinario per creare applicazioni IoT. Nel 2020, con l’aggiunta del protocollo GraphQL API, offre un modo ancora migliore per recuperare i dati.

Forniamo i nostri codici come primo passo per sviluppare le applicazioni desiderate.

Prerequisiti

L’unico prerequisito è completare il nostro tutorial Android QuickStart. A tale scopo, è necessario fare clic sul link indicato di seguito.

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

Ecco un tutorial su LiveQuery che sarà richiesto nella sezione 3 di questo tutorial della serie IoT. Tuttavia, non è necessario completarlo prima.

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

In caso di domande durante il tutorial, è possibile risolverle seguendo la guida ufficiale di Parse per Android al link sottostante.

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

Sezione 1: Nozioni di base sulla creazione dell’applicazione e sulla connessione con Back4App

In questo progetto, chiamiamo la nostra classe “MainActivity.java”. Ecco il codice di base che vi aiuterà a iniziare.

public class MainActivity extends AppCompatActivity {
   @Override
   protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
     // Inserisce la barra degli strumenti nell'applicazione. Ulteriori descrizioni di questa barra sono contenute nel file menu_main_activity.xml 
      Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
      setSupportActionBar(toolbar); 
   }
   @Override
   public boolean onCreateOptionsMenu(Menu menu) {
     // Gonfia il menu; questo aggiunge elementi alla barra delle azioni, se presente. 
      getMenuInflater().inflate(R.menu.menu_main_activity, menu);
     restituisce true;
   }
   @Override
   public boolean onOptionsItemSelected(MenuItem item) {
     // Gestisce qui i clic sugli elementi della barra delle azioni. La barra d'azione
      // gestirà automaticamente i clic sul pulsante Home/Up, a condizione che
      // purché si specifichi un'attività padre in AndroidManifest.xml. 
     int id = item.getItemId();
     //noinspection SimplifiableIfStatement 
     if (id == R.id.action_settings) {
        return true;
      }
      return super.onOptionsItemSelected(item);
   }
}

Potrebbe essere necessario aggiungere le seguenti importazioni:

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

Nel file XML del layout, assicurarsi di aggiungere il seguente codice:

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

Per collegare la vostra applicazione a Back4App, aggiungete il seguente codice all’interno della funzione onCreate() della vostra classe.

// Inizializzazione del server Parse
Parse.initialize(new Parse.Configuration.Builder(this)
    .applicationId("YOUR_APP_ID") // da Core Settings, in "Features" 
    .clientKey("YOUR_CLIENT_KEY") // da Impostazioni di base, su "Caratteristiche". 
    .server("https://parseapi.back4app.com/")
    .build()
);

Ricordarsi di seguire le istruzioni passo-passo di QuickStart per garantire l’accesso a Internet alla propria applicazione.

Sezione 2: Salvare e recuperare gli oggetti in Parse e visualizzarli nell’applicazione

Iniziate aggiungendo le importazioni seguenti

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

In questa sezione, salviamo gli oggetti della classe “CommandGPIO1” con un clic sul pulsante. Ogni oggetto Parse ha tre attributi predefiniti: objectId, createdAt e updatedAt. La nostra classe avrà altri due attributi: content e destination. I valori di content saranno “on” o “off” e rappresentano uno stato da inviare a un LED. Per quanto riguarda la destinazione, tutti gli oggetti conterranno “command”, ma potremmo definire stringhe diverse per i diversi LED.

Ad esempio:

Creiamo due pulsanti, ognuno dei quali scrive un valore diverso sul contenuto. Per creare un pulsante, aggiungere il seguente codice al file XML del layout:

<Pulsante
   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="Invia su"/>

Nel file di classe, la creazione del pulsante è semplice da scrivere:

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

Se siete alle prime armi con la programmazione Android, notate che l’argomento “findViewById” contiene “buttonSendOn”, che è l’id Android definito nell’XML.

Vogliamo che questo pulsante salvi un oggetto su Parse Server quando viene cliccato. Per farlo, aggiungere il seguente codice:

buttonOn.setOnClickListener( new OnClickListener(){
   @Override
  public void onClick(final View view) {
     // Creazione di un nuovo oggetto e assegnazione degli attributi appropriati 
      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, "Inviato ON all'output", Snackbar.LENGTH_LONG )
         .setAction("Action", null).show();
      }
   }); 
});

Si noti che è possibile aggiungere qualsiasi funzione si voglia eseguire all’interno della funzione di callback onClick. In questo caso, creiamo un oggetto di classe “CommandGPIO1”, impostiamo il contenuto come “on” e la destinazione come “command”.

È utile sapere che NON è necessario definire preventivamente questa classe nella dashboard di Parse! Se in seguito si decide di aggiungere un nuovo attributo o di cambiare il nome della classe, è sufficiente intervenire sul codice e le modifiche verranno aggiornate automaticamente sulla dashboard.

A questo punto, è meglio testare l’applicazione e verificare se l’oggetto viene creato!

Il SaveCallback mostra una snackbar, ovvero un feedback leggero nella parte inferiore dello schermo, come mostrato nella figura precedente.

Copiare questo codice per creare un pulsante che scriva gli oggetti con “off”. Modificare le seguenti righe nel layout XML

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

Creare un nuovo pulsante e modificare la riga in cui viene scritto il contenuto:

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

Ora che le cose funzionano correttamente su Parse Server, vogliamo fornire un feedback permanente all’utente dell’applicazione. A tale scopo, utilizzeremo l’elemento EditText, come mostrato di seguito.

Definire questo elemento nel layout XML:

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

Nello scope onCreate() della classe, aggiungere il seguente codice:

// Definizione dell'elemento di testo per mostrare lo stato del pin di uscita, che riceve i comandi dall'applicazione.
// I comandi sono inviati dai pulsanti, descritti in seguito in questo codice.
final EditText textOutPin = (EditText) findViewById(R.id.status1Text);
textOutPin.setFocusable(false);
textOutPin.setClickable(true);
textOutPin.setText("Caricamento dello stato del pin di uscita...");

La prima riga crea l’oggetto. La seconda riga lo rende non modificabile dagli utenti. La terza riga lo rende cliccabile, in modo che possa essere copiato e incollato. La quarta riga imposta il testo iniziale.

Vogliamo scrivere in questo oggetto EditText il campo contenuto dell’ultimo oggetto “CommandGPIO1” salvato in Parse. Anche se potremmo farlo utilizzando una logica all’interno del codice, in realtà recupereremo gli oggetti da Parse eseguendo una ParseQuery, poiché ciò fornisce risultati più realistici e robusti.

Il codice seguente dichiara, imposta i parametri e stampa i risultati di una ParseQuery.

ParseQuery<ParseObject> queryOut = ParseQuery.getQuery("CommandGPIO1");
queryOut.whereEqualTo("destinazione", "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("L'output è " + objects.get(0).getString("content"));
      }
     altrimenti{
         textOutPin.setText("Errore: " + e.getMessage());
      }
   }
});

La prima riga crea la query. La seconda aggiunge un vincolo per selezionare solo gli oggetti il cui campo di destinazione contiene “command”. La terza riga seleziona gli oggetti a partire da quelli più recenti. La quarta riga limita i risultati a uno, assicurando che verrà recuperato l’oggetto più recente.

La quinta riga attiva la query e richiama la funzione di callback al termine della stessa. Qui, scriviamo il contenuto dell’oggetto recuperato nell’oggetto EditText, definito in precedenza.

Aggiungeremo questo pezzo di codice subito dopo aver dichiarato l’oggetto EditText, in modo da eseguire una query all’apertura dell’applicazione e anche nella SaveCallback quando si salva un nuovo oggetto su Parse, per aggiornare il testo automaticamente quando si creano nuovi oggetti.

A questo punto, l’applicazione dovrebbe funzionare come illustrato nelle schermate seguenti.

Infine, aggiungiamo un pulsante di aggiornamento per consentire agli utenti di eseguire la query di cui sopra ogni volta che lo desiderano. Questo verrà fatto con un pulsante di stile diverso, cioè un pulsante di azione fluttuante.

Aggiungete questo codice al file XML del 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" />

Ora, nello scope onCreate() della classe, aggiungete il seguente codice:

// Pulsante di aggiornamento per ottenere lo stato dell'output quando richiesto.
// Qui viene eseguita la stessa query della prima
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("destinazione", "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("L'output è " + objects.get(0).getString("content"));
               }
              altrimenti{
                   textOutPin.setText("Errore: " + e.getMessage());
               }
               Snackbar.make(view, "Stato aggiornato dell'output", Snackbar.LENGTH_LONG ).setAction("Azione", null).show();
            }
        });
     }
 });

A questo punto, possiamo salvare gli oggetti su Parse Server, recuperare le informazioni da essi e visualizzarli nella nostra applicazione! L’applicazione dovrebbe funzionare come mostrato nella figura seguente:

Sezione 3: Ascolto degli eventi in tempo reale tramite Live Query e visualizzazione nell’applicazione

In questa sezione, monitoriamo in tempo reale la classe “InputGPIO” nella Parse Dashboard e visualizziamo il “contenuto” degli oggetti per l’utente.

Si inizia definendo un nuovo EditText nel layout XML:

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

Nello scope di onCreate(), creare un nuovo oggetto EditText ed eseguire subito dopo una query di inizializzazione (non ancora live).

final EditText textInPin = (EditText) findViewById(R.id.status2Text);
textInPin.setFocusable(false);
textInPin.setClickable(true);
textInPin.setText("Caricamento dello stato del pin di ingresso...");
// Query iniziale (non live) per ottenere l'ultimo stato memorizzato del pin
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) {
       if (e == null){
            textInPin.setText("L'input è " + objects.get(0).getString("content"));
        }
       altrimenti{
            textInPin.setText("Errore: " + e.getMessage());
        }
    }
});

Si noti che gli attributi di “InputGPIO” sono contenuto e tipo, quest’ultimo svolge il ruolo di destinazione nella classe “CommandGPIO”.

A questo punto, la vostra applicazione dovrebbe avere l’aspetto mostrato nella figura seguente.

Ora implementeremo la Live Query. A questo proposito, è necessario consultare il tutorial citato in precedenza.

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

Assicurarsi di seguire il passo 1 della nostra esercitazione sulla Live Query per selezionare le classi in cui la Live Query sarà abilitata e definire un nome di sottodominio. Potrebbe essere necessario creare la classe “InputGPIO” (se non esiste già) per abilitare la funzione. Seguire il passo 2 del tutorial per configurare il client Live Query su Android.

Ricordate di aggiungere le seguenti importazioni:

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

Aggiungere il seguente codice per inizializzare LiveQuery e definire i suoi parametri.

// Inizializzazione di LiveQuery
LiveQueryClient.init("wss:YOUR_SUBDOMAIN_NAME.back4app.io", "YOUR_APP_ID", true );
LiveQueryClient.connect();
// Definizione degli attributi di LiveQuery 
 Subscription subIn = new BaseQuery.Builder("InputGPIO")
    .where("type","interrupt")
    .addField("content")
    .build()
    .subscribe();

Nel definire la sottoscrizione, prendiamo solo gli elementi il cui attributo type è “interrupt” e recuperiamo il contenuto del campo.

Ora, aggiungiamo il seguente codice per rispondere ogni volta che un oggetto definito dalla sottoscrizione viene creato sul server Parse.

// Iniziando ad ascoltare gli eventi CREATE di LiveQuery, ottenendo il suo contenuto e scrivendolo
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("L'input è " + subInContent);
                   Snackbar.make(findViewById(R.id.myCoordinatorLayout), "Il pin di input è stato cambiato in " + subInContent.toUpperCase(), Snackbar.LENGTH_LONG ).setAction("Action", null).show();
                }
            });
        } catch (JSONException e){
            e.printStackTrace();
        }
    }
});

Il campo del contenuto dell’oggetto sarà visualizzato nel nostro nuovo elemento EditText.

Quanto fatto a questo punto è sufficiente per visualizzare qualsiasi input inviato dal dispositivo IoT. Tuttavia, per verificare il funzionamento dell’applicazione, implementeremo un altro tipo di pulsante, ossia un pulsante a levetta che creerà un oggetto di classe “InputGPIO” e lo salverà su Parse Server.

Aggiungere il seguente codice al file 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 = "input on
    android:textOff = "input off"/>.

Aggiungere il codice riportato di seguito nell’ambito della funzione onCreate(), nella classe MainActivity.

// Il pulsante Toggle è qui solo per emulare gli oggetti che l'hardware creerebbe
ToggleButton toggleTest = (ToggleButton) findViewById(R.id.toggleTest);
toggleTest.setOnClickListener( new OnClickListener(){
    @Override
   public void onClick(final View view) {
        ParseObject command = new ParseObject("InputGPIO");
       if(gpioStatusTest.equals("off"))
           gpioStatusTest = "on";
       altrimenti
            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();
            }
        });
    }
});

Inoltre, dichiarare la stringa nello scope MainActivity.

String gpioStatusTest = "off";

Abbiamo finito l’applicazione! Al termine di questa esercitazione, la vostra applicazione dovrebbe assomigliare alla figura riportata di seguito.

Se volete continuare a sviluppare la nostra applicazione IoT, leggete la nostra IoT Series, una guida passo-passo che vi insegna le basi per configurare un Raspberry Pi e applicazioni specifiche, come il recupero e il salvataggio di oggetti da Parse Server utilizzando JavaScript.

I nostri codici sono disponibili al seguente link:

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

Riferimenti

Connessione di Raspberry Pi a Parse Server

Configurazione di Raspberry Pi

Come far interagire un’app Android con un dispositivo IoT?

Sezione 1: Nozioni di base sulla creazione di un’app e sulla connessione con il lato server.
Sezione 2: Salvataggio e recupero di oggetti durante l’Parse e visualizzazione sull’app.
Sezione 3: Ascolto di eventi in tempo reale tramite query live e visualizzazione sull’app


Leave a reply

Your email address will not be published.