StaticFloat

Raspberry Pi über SSH mit dem Smartphone steuern

Ich bin bereits seit geraumer Zeit ein riesiger Fan des kleinen Mini-Computers Raspberry Pi und der enormen Möglichkeiten, die dieses kleine Wunderwerk eröffnet.
Selber habe ich meinen eigenen Raspberry Pi 2 erst seit etwa einem halben Jahr, doch bastel ich seit dem ersten Tag an munter an ihm herum.

Da ich nun im Eifer des Gefechts auf die Idee kam meinen Raspberry Pi Steckdosen ein- und ausschalten zu lassen, bin ich im Internet auf dieses Tutorial von tutorials-raspberrypi.de gestoßen.
Natürlich habe ich dieses Projekt direkt umgesetzt.

 

Zurück zum Thema:

Nun denn, genug um den heißen Brei herum geredet.
Eines verregneten Tages hatte ich dann die Idee meinen Raspberry Pi per Handy fernsteuern zu wollen.
Steckdosen ein, Steckdosen aus.
Es gibt im Google PlayStore bereits Apps, die es uns erlauben eine SSH Verbindung zu einem Linux Gerät aufzubauen. Leider sind diese Apps nicht darauf ausgelegt per einfachem Knopfdruck einen längeren Befehl auf dem Gerät auszuführen.

Wir wollen dies nun ändern!

 

Vorwort:

Wir werden in diesem Artikel ebenfalls SSH für unsere Verbindung zum Raspberry Pi nutzen, allerdings auf Tastendruck nur eine Verbindung aufbauen, um einen Befehl zu senden und die Verbindung anschließend wieder schließen.
Das Protokoll von SSH ist nicht sonderlich einfach zu programmieren, weshalb wir auf eine bereits bestehende Bibliothek (JSch) zurückgreifen werden.
Bitte versteht, dass diese Anleitung zwar so einfach wie möglich gehalten ist, aber tiefgreifendere Programmierung darstellt. Vor allem aber wird dieser Artikel mehr als nur ein paar kleine Worte (nach dem fertigen Schreiben dieses Artikels sind es ganze 4561 Worte) umfassen und meines Erachtens nach recht mächtig werden. Ich hoffe ihr habt etwas Geduld mit gebracht.

 

Vorbereitung:

Bevor wir anfangen können zu programmieren ist es zwingend von Nöten die bereits angesprochene Bibliothek in Android zu importieren.
Diese Bibliothek wird uns viel Arbeit abnehmen.

Als erstes müssen wir JSch herunterladen.
http://sourceforge.net/projects/jsch/files/jsch.jar/0.1.53/jsch-0.1.53.jar/download

Ist dies geschehen verschieben wir die Datei in den Ordner app -> libs.

Abschließend muss noch „Sync Project with Gradle Files“ in der oberen Leiste von Android Studio gewählt werden.

 

Programmierung:

Die Vorarbeit ist geleistet. Fangen wir an zu programmieren!

Als Erstes benötigen wir ein paar wenige Imports.
Diese fügen wir direkt unterhalb unseres Packagepfades, in die zu benutzende Java Datei, ein:

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;

import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Properties;

 

Wir erstellen uns nun eine eigene Funktion. Wir nennen diese „sshBefehl“:

public static ArrayList<String> sshBefehl(String username, String passwort, String hostname, int port, String befehl) throws Exception {
   //Hier den Code einfügen.
}

Dieser Funktion können wir dann später die entsprechenden Nutzer- und Verbindungsdaten und den Befehl übergeben.
Sie soll unseren Befehl per SSH ausführen, die Ausgabe der Konsole zurückliefern und bei einem Fehler eine Meldung ausgeben („throws Exception“).

 

Verbindung aufbauen und Befehl senden:

Unsere Aufgabe gestaltet sich nun etwas komplexer.
Als Erstes müssen wir versuchen eine Verbindung zu unserem Raspberry Pi aufzubauen, anschließend müssen wir noch einen Channel öffnen, indem wir den Befehl senden. Erst innerhalb unseres Channels können wir Ausgaben der Konsole protokollieren.

Wir benötigen nun ein Objekt aus der Library „JSch“, um eine Verbindung aufbauen zu können:

// Erstelle das benötigte Objekt, um eine SSH-Verbindung aufbauen zu können.
JSch jsch = new JSch();

 

Nun können wir dieses Objekt mit den Verbindungs- und Nutzerdaten füttern und eine Verbindung aufbauen. Der Einfachheit halber verzichten wir auf die Verifizierung des Keys. Damit prüfen wir nicht mehr auf Man-In-Middle Angriffe, aber haben es deutlich einfacher eine Verbindung zu öffnen. Im heimischen Netzwerk ist die Gefahr eines solchen Angriffes ohnehin sehr gering.

// Füttere das Objekt mit allen nötigen Informationen, um eine Verbindung aufbauen zu können.
Session session = jsch.getSession(username, hostname, port);
session.setPassword(passwort);

// Umgehe das Abgleichen nach dem richtigen Key. (Es wird nun eine Man-In-Middle Attacke nicht mehr abgefangen)
Properties prop = new Properties();
prop.put("StrictHostKeyChecking", "no");
session.setConfig(prop);

// Stelle eine Verbindung her.
session.connect();

 

Da wir idealerweise bereits erfolgreich eine Verbindung zu unserem Raspberry geöffnet haben können wir mit den erhaltenen Daten einen Channel öffnen:

// Erstelle ein neues Objekt, für einen neuen SSH Channel.
ChannelExec channelssh = (ChannelExec) session.openChannel("exec");

// Füttere den Channel mit dem Befehl und schicke den Befehl ab.
channelssh.setCommand( befehl );
channelssh.connect();

 

Eigentlich sind wir bereits fertig.
Doch der Sicherheit und der Performance zu Liebe sollten wir den bestehenden Channel und die bestehende Verbindung noch schließen.

// Beende alle Verbindungen.
channelssh.disconnect();
session.disconnect();

 

Aber Achtung!
Wir überprüfen bei der Verbindung über SSH nicht, ob die Keys stimmen.
Wir können so also keinen Man-in-the-Middle Angriff abfangen.
Des Weiteren sollte unser Raspberry Pi am besten eine statische IP haben.

 

Konsolenausgabe protokollieren:

Zwar ist unsere Funktion vom Prinzip her fertig, doch haben wir oben definiert, dass wir ein Array zurückgeben wollen.
Dieses Array soll alle Zeilen der Konsolenausgabe der Reihe nach enthalten.

An diesem Punkt wird es wieder etwas Tricky.
Wir müssen nämlich während der Protokollierung auf mehrere Fehler gleichzeitig prüfen.
Ist die Zeile leer? Wurde die Zeile bereits protokolliert? Wurden wir ausgeloggt? Ist die Verbindung abgebrochen? Ist der Befehl beendet?
Ich werde hier an dieser Stelle nicht großartig auf den Code eingehen, sondern nur zur Verfügung stellen. Er ist mit Kommentaren versehen.

Unterhalb von „JSch jsch = new JSch();“ einfügen:

// Bereite ein paar Variablen vor, um Ausgaben der Konsole auslesen zu können.
byte[] buffer = new byte[1024];
ArrayList<String> lines = new ArrayList<>();

 

Zwischen „channelssh.connect();“ und „channelssh.disconnect();“ einfügen:

// Fange an die Ausgaben der Konsole auszulesen.
try {
    InputStream in = channelssh.getInputStream();
    String line = "";

    // Lese alle Ausgaben aus, bis der Befehl beendet wurde oder die Verbindung abbricht.
    while (true) {

        // Schreibe jede Zeile der Konsolenausgabe in unser Array.
        while (in.available() > 0) {
            int i = in.read(buffer, 0, 1024);

            // Brich die Protokollierung der Ausgabe, für diese Zeile, ab, wenn die Ausgabe leer sein sollte.
            if (i < 0) {
                break;
            }

            line = new String(buffer, 0, i);
            lines.add(line);
        }

        // Wir wurden ausgeloggt.
        if (line.contains("logout")) {
            break;
        }

        // Befehl beendet oder Verbindung abgebrochen.
        if (channelssh.isClosed()) {
            break;
        }

        // Warte einen kleinen Augenblick mit der nächsten Zeile.
        try {
            Thread.sleep(1000);
        } catch (Exception ee) {}
    }
}catch (Exception e){}

 

Unterhalb von „session.disconnect();“ einfügen:

// Gib die Ausgabe zurück
return lines;

 

Funktion starten:

Eine Kleinigkeit fehlt uns noch zu vollkommenem Glück.
Wie auch die Programmierung der eigentlichen Funktion etwas Tricky war, ist auch die Programmierung des Funktionsstarts etwas umständlicher.

Ich sollte etwas weiter ausholen.
Wir bewegen uns hier in einer Programmierung die Zeit benötigt.
Je nach Internetverbindung benötigen wir Zeit eine Verbindung aufzubauen.
Wir benötigen zusätzlich Zeit einen Channel zu öffnen.
Wie sollte es anders sein, benötigen wir wahrscheinlich noch Sekunden dafür die Verbindungen wieder zu schließen.
Ganz zu schweigen von der Performance des Raspberry Pi und der darauf ausgeführten Befehle.

Führen wir die Funktion „sshBefehl“ auf unserem Smartphone ganz normal aus, so müssen wir davon ausgehen, dass es Sekunden bis Minuten dauern wird bis es wieder reagiert.
Was können wir nun tun?

Ganz einfach!
Wir führen unsere Funktion in einem BackgroundThread aus.
Wir erstellen also eine Hintergrundaufgabe, um das Einfrieren unseres Smartphones zu verhindern.

// Hintergrundaufgabe erstellen
new AsyncTask<Integer, Void, Void>() {
    @Override
    protected Void doInBackground(Integer... params) {
        try {

            // Funktion ausführen und Konsolenausgabe in "lines" speichern.
            ArrayList<String> lines = sshBefehl("Nutzername", "Passwort", "IP-Adresse", Port, "Befehl");

            // Alle Zeilen der Konsolenausgabe in den Android Logs ausgeben.
            while(!lines.isEmpty()){
                Log.e("Rückgabe", lines.get(0));
                lines.remove(0);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}.execute(1);

 

 

Wie geht es weiter ?

Wir haben es ja bereits geschafft eine komplette Funktion zu schreiben, welche es uns erlaubt längere Befehle auf unserem SSH Server auszuführen.
Bisher ist die Funktion aber eher etwas zusammenhangslos gestaltet und ohne zugehörige App.

Auf den nächsten Seiten dieses Artikels werden wir zusammen die zugehörige App dazu schreiben.
Eine fertige App werde ich ebenfalls im Google PlayStore veröffentlichen.

Ich hoffe ihr lasst euch nicht entmutigen.

Melvin

Ich bin 23 Jahre jung und studiere zurzeit Wirtschaftsinformatik an der Georg-August-Universität in Göttingen. Ich bin ein Mensch, der sich neben der Programmierung noch für tausend andere Dinge interessiert, die mal mehr und mal weniger verrückt sind. Vor allem aber bin ich Feuer und Flamme mit der Programmierung von eigenen kleinen Apps und Programmen, die mein Leben bereichern.

Schreibe den ersten Kommentar

*Pflichtfeld

3D Drucker – Die häufigsten Probleme

Du hast Probleme mit deinem Drucker?
Vielleicht hilft dir diese Seite, mit Problemen und Lösungen zum Druck von Modellen.

Auf geht's!