Android-App für Raspberry Pi

In unseren vorherigen Tutorials zum Thema IoT haben wir beschrieben, wie man einen Raspberry Pi einrichtet und ihn über die Back4App API mit dem Parse Server verbindet, um Objekte auf dem Server zu speichern und Abfragen und Live-Abfragen durchzuführen.

Jetzt behandeln wir, wie man alles, was auf der Raspberry-Seite gemacht wurde, auf die App-Seite überträgt. In diesem Tutorial beschreiben wir eine Android-App, die mit dem zuvor konfigurierten IoT-Gerät interagiert. Auf der Parse-Server-Seite führt die App die gleichen Aufgaben aus wie der Raspberry: Sie schreibt Objekte und führt Abfragen und Live-Abfragen durch. Bitte beachten Sie, dass diese Funktionalitäten auch dann nützlich sein können, wenn Sie nicht vorhaben, eine IoT-Anwendung zu entwickeln!

Parse hat sich als ein hervorragendes Framework für die Entwicklung von IoT-Anwendungen erwiesen. Im Jahr 2020 bietet es mit der Erweiterung um das GraphQL-API-Protokoll eine noch bessere Möglichkeit, Daten abzurufen.

Wir stellen unsere Codes als ersten Schritt zur Verfügung, damit Sie Ihre gewünschten Apps entwickeln können.

Voraussetzungen

Die einzige Voraussetzung ist, dass Sie unser Android QuickStart-Tutorial absolviert haben. Dazu müssen Sie auf den unten stehenden Link klicken.

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

Hier finden Sie ein Tutorial zu LiveQuery, das in Abschnitt 3 dieses IoT Series Tutorials benötigt wird. Sie müssen es jedoch nicht vorher abschließen.

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

Wenn Sie während des Tutorials Fragen haben, können Sie diese mit Hilfe des offiziellen Parse-Leitfadens für Android unter dem folgenden Link klären.

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

Abschnitt 1: Grundlagen zur Erstellung Ihrer App und Verbindung mit Back4App

In diesem Projekt nennen wir unsere Klasse “MainActivity.java”. Hier ist der grundlegende Code, der Ihnen den Einstieg erleichtern wird.

public class MainActivity extends AppCompatActivity {
   @Override
   protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
     // Symbolleiste in die Anwendung einfügen. Weitere Beschreibung dieser Leiste in der Datei menu_main_activity.xml 
      Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
      setSupportActionBar(toolbar); 
   }
   @Override
   public boolean onCreateOptionsMenu(Menu menu) {
     // Aufblasen des Menüs; dies fügt Elemente zur Aktionsleiste hinzu, wenn diese vorhanden ist. 
      getMenuInflater().inflate(R.menu.menu_main_activity, menu);
     return true;
   }
   @Override
   public boolean onOptionsItemSelected(MenuItem item) {
     // Hier werden Klicks auf Elemente der Aktionsleiste behandelt. Die Aktionsleiste wird
      // Klicks auf die Schaltfläche "Home/Up" automatisch verarbeiten, sofern
      // sofern Sie eine übergeordnete Aktivität in AndroidManifest.xml angeben. 
     int id = item.getItemId();
     //keineÜberprüfung EinfachesIfStatement 
     if (id == R.id.action_settings) {
        return true;
      }
      return super.onOptionsItemSelected(item);
   }
}

Möglicherweise müssen Sie die folgenden Importe hinzufügen:

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;
android.view.View.OnClickListenerimportieren ;

Fügen Sie in Ihrer Layout-XML-Datei den folgenden Code ein:

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

Um Ihre App mit Back4App zu verbinden, fügen Sie den folgenden Code in die onCreate()-Funktion Ihrer Klasse ein.

// Initialisierung des Parse-Servers
Parse.initialize(new Parse.Configuration.Builder(this)
    .applicationId("YOUR_APP_ID") // aus Core Settings, auf "Features" 
    .clientKey("YOUR_CLIENT_KEY") // aus den Grundeinstellungen, unter "Features" 
    .server("https://parseapi.back4app.com/")
    .build()
);

Denken Sie daran, die Schritt-für-Schritt-Anleitung in QuickStart zu befolgen, um Ihrer Anwendung den Internetzugang zu gewähren.

Abschnitt 2: Speichern und Abrufen von Objekten beim Parse und Anzeigen in der App

Beginnen Sie mit dem Hinzufügen der folgenden Importe

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 com.parse;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.List;

In diesem Abschnitt speichern wir Objekte der Klasse “CommandGPIO1” mit einem Tastenklick. Jedes Parse-Objekt hat drei Standardattribute: objectId, createdAt und updatedAt. Unsere Klasse wird zwei weitere Attribute haben: content und destination. Die Werte für content sind entweder “on” oder “off” und stellen einen Status dar, der an eine LED gesendet wird. Für destination werden alle Objekte “command” enthalten, aber wir können für verschiedene LEDs unterschiedliche Zeichenketten definieren.

Ein Beispiel:

Wir erstellen zwei Schaltflächen, und jede von ihnen schreibt einen anderen Wert auf den Inhalt. Um eine Schaltfläche zu erstellen, fügen Sie den folgenden Code in Ihre Layout-XML-Datei ein:

<Button
   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="Senden Ein"/>

In Ihrer Klassendatei ist das Erstellen der Schaltfläche so einfach wie das Schreiben:

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

Wenn Sie ein Anfänger in der Android-Programmierung sind, beachten Sie bitte, dass das Argument “findViewById” “buttonSendOn” enthält, und dies ist die Android:Id, die wir in der XML-Datei definiert haben.

Wir möchten, dass diese Schaltfläche ein Objekt auf dem Parse-Server speichert, wenn sie angeklickt wird. Fügen Sie dazu den folgenden Code ein:

buttonOn.setOnClickListener( new OnClickListener(){
   @Override
  public void onClick(final View view) {
     // Erstellen eines neuen Objekts und Zuweisen der richtigen Attribute 
      ParseObject command = new ParseObject("CommandGPIO1");
      command.put("Inhalt","on");
      befehl.put("Ziel", "Befehl");
      command.saveInBackground(new SaveCallback(){
      @Override
     public void done(ParseException e){
         Snackbar.make(view, "Eingeschaltet zur Ausgabe", Snackbar.LENGTH_LONG )
         .setAction("Aktion", null).show();
      }
   }); 
});

Beachten Sie, dass wir innerhalb der onClick-Callback-Funktion jede beliebige Funktion hinzufügen können, die wir ausführen möchten. Hier erstellen wir ein Objekt der Klasse “CommandGPIO1”, setzen den Inhalt auf “on” und das Ziel auf “command”.

Es ist nützlich zu wissen, dass wir diese Klasse NICHT vorher auf dem Parse-Dashboard definieren müssen! Wenn Sie später ein neues Attribut hinzufügen oder den Namen Ihrer Klasse ändern möchten, brauchen Sie nur Ihren Code zu bearbeiten, und die Änderungen werden automatisch auf dem Dashboard aktualisiert.

An diesem Punkt sollten Sie Ihre Anwendung besser testen und prüfen, ob das Objekt erstellt wird!

Der SaveCallback zeigt eine Snackbar an, eine leichtgewichtige Rückmeldung am unteren Rand Ihres Bildschirms, wie in der Abbildung oben dargestellt.

Kopieren Sie diesen Code, um eine Schaltfläche zu erstellen, die Objekte mit “off” schreibt. Ändern Sie die folgenden Zeilen in der Layout-XML

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

Erstellen Sie eine neue Schaltfläche und ändern Sie die Zeile, in die der Inhalt geschrieben wird:

Button buttonOff = (Button) findViewById(R.id.buttonSendOff);
command.put("Inhalt","Aus");

Nun, da die Dinge auf dem Parse-Server richtig funktionieren, wollen wir dem Benutzer der App eine permanente Rückmeldung geben. Dazu verwenden wir das EditText-Element, wie unten gezeigt.

Definieren Sie dieses Element in der 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" />

Fügen Sie innerhalb des onCreate()-Bereichs Ihrer Klasse den folgenden Code hinzu:

// Definition eines Textelements zur Anzeige des Status des Ausgabepins, der Befehle von der App empfängt
// Die Befehle werden über Schaltflächen gesendet, die später in diesem Code beschrieben werden.
final EditText textOutPin = (EditText) findViewById(R.id.status1Text);
textOutPin.setFocusable(false);
textOutPin.setClickable(true);
textOutPin.setText("Status des Ausgangspins wird geladen...");

In der ersten Zeile wird das Objekt erstellt. Die zweite Zeile macht es für den Benutzer nicht editierbar. Die dritte Zeile macht es anklickbar, so dass es kopiert und eingefügt werden kann. In der vierten Zeile wird der Anfangstext festgelegt.

Wir wollen das Inhaltsfeld des letzten “CommandGPIO1”-Objekts, das beim Parsen gespeichert wurde, in dieses EditText-Objekt schreiben. Obwohl wir dies mit Hilfe einer Logik innerhalb des Codes tun könnten, werden wir tatsächlich Objekte von Parse abrufen, indem wir eine ParseQuery durchführen, da dies realistischere und robustere Ergebnisse liefert.

Der folgende Code deklariert, setzt Parameter und gibt die Ergebnisse einer Parse-Abfrage aus.

ParseQuery<ParseObject> queryOut = ParseQuery.getQuery("CommandGPIO1");
queryOut.whereEqualTo("Ziel", "Befehl");
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("Ausgabe ist " + objects.get(0).getString("content"));
      }
     else{
         textOutPin.setText("Fehler: " + e.getMessage());
      }
   }
});

In der ersten Zeile wird die Abfrage erstellt. Die zweite Zeile fügt eine Einschränkung hinzu, um nur die Objekte auszuwählen, deren Zielfeld “command” enthält. In der dritten Zeile werden die Objekte beginnend mit den neuesten Objekten ausgewählt. Die vierte Zeile schränkt die Ergebnisse auf eins ein, um sicherzustellen, dass wir das neueste Objekt abrufen.

Die fünfte Zeile aktiviert die Abfrage und ruft die Callback-Funktion auf, wenn sie beendet ist. Hier schreiben wir den Inhalt des abgerufenen Objekts in das zuvor definierte EditText-Objekt.

Wir fügen dieses Codestück direkt nach der Deklaration des EditText-Objekts ein, so dass beim Öffnen der App eine Abfrage durchgeführt wird, und auch im SaveCallback, wenn ein neues Objekt bei Parse gespeichert wird, um den Text beim Erstellen neuer Objekte automatisch zu aktualisieren.

Jetzt sollte Ihre Anwendung so funktionieren, wie in den folgenden Bildschirmabbildungen dargestellt.

Schließlich fügen wir eine Aktualisierungsschaltfläche hinzu, damit die Benutzer die obige Abfrage jederzeit durchführen können. Dies geschieht mit einer anderen Art von Schaltfläche, d. h. einer schwebenden Aktionsschaltfläche.

Fügen Sie diesen Code in die Layout-XML-Datei ein:

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

Fügen Sie nun innerhalb des onCreate()-Bereichs der Klasse den folgenden Code hinzu:

// Aktualisieren Sie die Schaltfläche, um den Ausgabestatus auf Anfrage zu erhalten.
// Hier wird die gleiche Abfrage wie bei der ersten durchgeführt
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("Ziel", "Befehl");
        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("Ausgabe ist " + objects.get(0).getString("content"));
               }
              else{
                   textOutPin.setText("Fehler: " + e.getMessage());
               }
               Snackbar.make(view, "Aktualisierter Status der Ausgabe", Snackbar.LENGTH_LONG ).setAction("Action", null).show();
            }
        });
     }
 });

Jetzt können wir Objekte auf dem Parse-Server speichern, Informationen von ihnen abrufen und sie in Ihrer Anwendung anzeigen! Ihre Anwendung sollte nun wie in der Abbildung unten dargestellt funktionieren:

Abschnitt 3: Abhören von Echtzeit-Ereignissen mit Live Query und Anzeige in der App

In diesem Abschnitt überwachen wir die Klasse “InputGPIO” im Parse Dashboard in Echtzeit und zeigen den “Inhalt” der Objekte für den Benutzer an.

Beginnen Sie mit der Definition eines neuen EditTextes im 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" />

Erstellen Sie im Rahmen von onCreate() ein neues EditText-Objekt und führen Sie unmittelbar danach eine Initialisierungsabfrage (noch nicht live) durch.

final EditText textInPin = (EditText) findViewById(R.id.status2Text);
textInPin.setFocusable(false);
textInPin.setClickable(true);
textInPin.setText("Status des Eingangspins wird geladen...");
// Initiale (nicht live) Abfrage, um den zuletzt gespeicherten Status des Pins zu erhalten
ParseQuery<ParseObject> queryIn = ParseQuery.getQuery("InputGPIO");
queryIn.whereEqualTo("Typ", "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("Eingabe ist " + objects.get(0).getString("Inhalt"));
        }
       else{
            textInPin.setText("Fehler: " + e.getMessage());
        }
    }
});

Beachten Sie, dass die Attribute von “InputGPIO” content und type sind, wobei letzteres die Rolle des Ziels für die Klasse “CommandGPIO” spielt.

Zu diesem Zeitpunkt sollte Ihre Anwendung wie in der folgenden Abbildung dargestellt aussehen.

Nun werden wir die Live-Abfrage implementieren. Hierfür wird das bereits erwähnte Tutorial benötigt.

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

Stellen Sie sicher, dass Sie Schritt 1 in unserem Live Query-Tutorial befolgen, um auszuwählen, in welchen Klassen Live Query aktiviert werden soll, und um einen Subdomainnamen zu definieren. Möglicherweise müssen Sie die Klasse “InputGPIO” erstellen (falls sie noch nicht vorhanden ist), um die Funktion für sie zu aktivieren. Folgen Sie Schritt 2 des Tutorials, um den Live Query Client auf Android einzurichten.

Denken Sie daran, die folgenden Importe hinzuzufügen:

import tgio.parselivequery.BaseQuery;
importieren Sie tgio.parselivequery.LiveQueryClient;
importieren Sie tgio.parselivequery.LiveQueryEvent;
import tgio.parselivequery.Subscription;import tgio.parselivequery;
importieren tgio.parselivequery.interfaces.OnListener;

Fügen Sie den folgenden Code hinzu, um LiveQuery zu initialisieren und seine Parameter zu definieren.

// Initialisierung von LiveQuery
LiveQueryClient.init("wss:YOUR_SUBDOMAIN_NAME.back4app.io", "YOUR_APP_ID", true );
LiveQueryClient.connect();
// Definieren der Attribute von LiveQuery 
 Subscription subIn = new BaseQuery.Builder("InputGPIO")
    .where("Typ","Interrupt")
    .addField("Inhalt")
    .build()
    .subscribe();

Bei der Definition des Abonnements nehmen wir nur die Elemente, deren Typattribut “interrupt” ist, und rufen den Feldinhalt ab.

Fügen Sie nun den folgenden Code hinzu, um zu reagieren, wenn ein durch das Abonnement definiertes Objekt auf dem Parse-Server erstellt wird.

// Wir fangen an, auf LiveQuery CREATE-Ereignisse zu hören, seinen Inhalt abzurufen und zu schreiben
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("Eingabe ist " + subInContent);
                   Snackbar.make(findViewById(R.id.myCoordinatorLayout), "Eingabepin wurde geändert in " + subInContent.toUpperCase(), Snackbar.LENGTH_LONG ).setAction("Action", null).show();
                }
            });
        } catch (JSONException e){
            e.printStackTrace();
        }
    }
});

Das Inhaltsfeld des Objekts wird in unserem neuen EditText-Element angezeigt.

Was wir bis zu diesem Punkt getan haben, reicht aus, um jede vom IoT-Gerät gesendete Eingabe anzuzeigen. Um jedoch zu überprüfen, wie die App funktioniert, werden wir eine andere Art von Schaltfläche implementieren, d. h. eine Umschalttaste, die ein Objekt der Klasse “InputGPIO” erstellt und auf dem Parse-Server speichert.

Fügen Sie den folgenden Code in die Layout-XML-Datei ein:

<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 = "Eingabe ein"
    android:textOff = "eingabe aus"/>

Fügen Sie den unten aufgeführten Code in den Bereich der Funktion onCreate() in der Klasse MainActivity ein.

// Der Toggle-Button dient nur dazu, die Objekte zu emulieren, die die Hardware erzeugen würde
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 = "ein";
       sonst
            gpioStatusTest = "aus";
        command.put("type","interrupt");
        command.put("Inhalt", "Von Toggle: " + gpioStatusTest);
        command.saveInBackground(new SaveCallback(){
            @Override
           public void done(ParseException e){
                Snackbar.make(view, "Geänderter Eingabepin", Snackbar.LENGTH_LONG ).setAction("Action", null).show();
            }
        });
    }
});

Deklarieren Sie außerdem den String im Bereich MainActivity.

String gpioStatusTest = "aus";

Wir sind fertig mit der App! Am Ende dieses Tutorials sollte Ihre App ähnlich aussehen wie in der Abbildung unten.

Wenn Sie mit der Entwicklung unserer IoT-Anwendung fortfahren möchten, lesen Sie bitte unsere IoT-Serie, eine Schritt-für-Schritt-Anleitung, in der Sie die Grundlagen der Einrichtung eines Raspberry Pi und spezifischer Anwendungen lernen, wie z. B. das Abrufen und Speichern von Objekten von Parse Server mit JavaScript.

Unsere Codes sind unter dem folgenden Link verfügbar:

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

Referenzen

Verbindung zwischen Raspberry Pi und Parse Server

Einrichten des Raspberry Pi

Wie interagiert eine Android-App mit einem IoT-Gerät?

– Abschnitt 1: Grundlagen zur App-Erstellung und Serververbindung.
– Abschnitt 2: Objekte beim Parse speichern und abrufen und in der App anzeigen.
– Abschnitt 3: Echtzeit-Ereignisse mit Live-Abfragen abhören und in der App anzeigen.


Leave a reply

Your email address will not be published.