SSH App programmieren

maksimilian

Mitglied
Hallo Ihr,
ich möchte für eine Pi-Anwendung (Türsteuerung) eine Fernbedienung mittels einer Handy-App (Samsung SM-A310F) implementieren. Mir ist bekannt, dass im PlayStore haufenweise SSH-Button Apps angeboten werden. Für Tests ist bei mir auch so eine App in Verwendung. Ich möchte aber schon allein wegen fachlicher Neugier die App selber implementieren und dabei auch eigene Vorstellungen berücksichtigen. Die Start-Funktionalität bestünde in einer Oberfläche mit einigen Buttons, hinter welchen sich der Aufruf eines Python-Skripts im Pi-System verbergen würde, welches über eine Pipe mit der Steuerung kommuniziert.

Da ich sowohl mit Kotlin/Java/notwendige Bibliotheken und Android Studio noch nicht so vertraut bin, bräuchte ich Start-Hilfe. Eigentlich wollte ich die Implementierung mit Kotlin vornehmen, habe aber bei meiner Recherche für SSH noch keinen Ansatz gefunden. Interessant wäre für mich in dem Zusammenhang, ob Java in Kotlin aufgerufen werden kann.

Bin dann auf diesen Link gestoßen, der sich aber auf einen älteren Thread bezieht.

maksimilian
 

Robert Zenz

Top Contributor
Die bessere Variante waere es, wenn du am Pi einen Server hast welcher eine API zur Verfuegung stellt, und deine App kommuniziert dann nur mit dem Server. Hat einige Vorteile, aber einer der wichtigsten ist dass deine App dann keinen SSH Zugang auf die Schuessel braucht, und die Kommandoes welche ueber die Apps kommen koennen sind gut definiert.
 

maksimilian

Mitglied
Danke für den Hinweis, Robert. Dass der Zugriff auf den Pi mal anders organisiert werden sollte, ist mir schon klar. Aber ich möchte schrittweise vorgehen, ert mal die SSH-App.
 
K

kneitzel

Gast
Bezüglich Zugriff auf Java empfehle ich einfach einmal:

Des Weiteren hast Du bei der Android App Entwicklung mit Kotlin doch auch in der Regel Gradle. Du kannst also ganz normal die Dependencies zu den Libraries, die du brauchst, eintragen.

Daher kannst Du da auch einfach jsch für nutzen:

Evtl. ist es einfacher, erst einmal auf dem PC damit zu spielen um dann den Wechsel hin zu Android zu machen, wenn Du die Library etwas kennen gelernt hast. Da ist es halt einfacher, da Du alles direkt in der IDE ausführen kannst ohne Android VM oder smartphone.
 

maksimilian

Mitglied
Ich verwende in MainActivity.kt

import com.jcraft.jsch.ChannelExec

und erhalte die Fehlermeldung Unresolved reference: jcraft

Wo müssen die Klassen-Definitionen von jsch in der Datei/Projekt-Hierarchie stehen ?
 

maksimilian

Mitglied
Danke für den Tipp, ich kriege aber die korrekte Syntax nicht hin. Es gibt die Datei jsch-0.1.55.jar unter $HOME/android-studio/lib/jsch-0.1.55.jar, welche man sich auch von mvnrepository.com herunterladen kann. Ich ergänze die Datei build.gradle wie folg (ins Blaue):

Java:
 dependencies {
        classpath "com.android.tools.build:gradle:4.1.2"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "com.jcraft.jsch:jsch-0.1.55"
}

Build Output ist:
! Build: failed at .. with 1 error:
! Could not resolve com.jcraft.jsch:jsch-0.1.55:
! Could not find com.jcraft.jsch:jsch-0.1.55:

Was muss hinter dem Doppelpunkt stehen ? Oder muss ich ein repository definieren (und dann wie)?
 
K

kneitzel

Gast
Der Ablauf ist nicht, dass Du die jar Datei von Hand herunter lädst - Das macht gradle automatisch.

Den richtigen Eintrag zeigt Dir das mvnrepository auch (daher der Link):
- Klicke dort erst auf die Version, die Du nutzen willst (also am Besten die aktuelle: 0.1.55
- Dann hast Du auf der Seite unter der kleinen Tabelle eine reihe Tabs: Maven, Gradle, ... Dort mal auf Gradle gehen
Java:
// https://mvnrepository.com/artifact/com.jcraft/jsch
implementation group: 'com.jcraft', name: 'jsch', version: '0.1.55'

Damit hast Du dann schon den Eintrag, der notwendig ist. Genau das kannst Du 1:1 in die dependencies kopieren.

Aber Du bist vermutlich auch im falschen build.gradle File. Du dürftest derzeit in dem Hauptfile sein. Durch classpath werden jetzt nur Projekt Abhängigkeiten für Plugins und so definiert. Das sind jetzt keine Compile-Zeit Abhängigkeiten.
Du hast ein Unterverzeichnis (Vermutlich "app") mit einem weiteren build.gradle und in dem musst Du die Abhängigkeit wie oben abgeben.

Daher hattet Du vermutlich auch einen Kommentar bei diesen Kommentaren wie den hier:

Java:
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files

:)
 

maksimilian

Mitglied
Der Tipp bringt mich weiter. Jetzt habe ich das Problem, dass der Verbindungsaufbau scheitert. Ich verwende folgendes Testprogramm

Java:
package com.example.ereignisse

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.ereignisse.databinding.ActivityMainBinding

import com.jcraft.jsch.ChannelExec
import com.jcraft.jsch.JSch
import com.jcraft.jsch.Session
import java.io.ByteArrayOutputStream
import java.util.*

class MainActivity : AppCompatActivity() {
    private lateinit var B: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        B = ActivityMainBinding.inflate(layoutInflater)
        setContentView(B.root)

        B.SSH.setOnClickListener {
            B.Ausgabe.text = "SSH betaetigt"
            executeRemoteCommand(
                    "pi",
                    "pass",
                    "192.168.178.32",
                    22,
                    "ps>x"
            )
        }
    }


    fun executeRemoteCommand(
            username: String?,
            password: String?,
            hostname: String?,
            port: Int,
            message: String?
    ): String? {
        val jsch = JSch()
        val session: Session = jsch.getSession(username, hostname, port)
        session.setPassword(password)

        // Avoid asking for key confirmation
        val prop = Properties()
        prop["StrictHostKeyChecking"] = "no"
        session.setConfig(prop)
        try {
            session.connect()
        }
        catch (ex:Exception) {
                B.Ausgabe.text = ex.toString()
        }

        // SSH Channel
        val channelssh: ChannelExec = session.openChannel("exec") as ChannelExec
        val baos = ByteArrayOutputStream()

        try {
            channelssh.setOutputStream(baos)
            // Execute command
            channelssh.setCommand(message)
            channelssh.connect()
            channelssh.disconnect()
            }
        catch (ex:Exception) {
            B.Ausgabe.text = ex.toString()
        }
        finally {
            return baos.toString()
        }

    }

Der Button SSH löst einen SSH-Aufruf aus. Trotz Abschalten der Key-Abfrage scheitert der connect in Zeile 50 mit der Exception-Meldung:
Java:
com.jcraft.jsch.JSchException: java.net.SocketException: Permission denied

Bei der SSH-Button App, welche ich bisher zum Testen vewende, funktioniert die Verbindung mit den gleichen Verbindungsdaten.
 
K

kneitzel

Gast
Du machst es ja in einer Android Applikation. Hast Du die notwendigen Rechte im Manifest eingetragen?
 

mihe7

Top Contributor
#13 DieException vom connect() meldete "Permission denied", was ja alles Mögliche bedeuten kann
Ja, ich meinte nur, ob man im Logcat nicht mehr sieht. Bin schon eine Zeit lang nicht mehr an Android gesessen, meinte mich aber zu erinnern, dass da durchaus Hinweise auf das Manifest zu finden waren. Abgesehen davon: permission denied ist schon ein guter Indikator, dass man sich mal die Rechte ansieht :)
 

maksimilian

Mitglied
#16 Hatte in #13 die falsche Antwort gegeben. Sorry, ich lerne noch. Ich habe jetzt mal den Fehler rekonstruiert und im Logcat verfolgt. Da ist mir aber ein Hinweis auf Manifest nicht gelungen. Das kann aber auch daran liegen, dass ich die Hinweise auf bestimmte Zeilen in bestimmten Modulen (z.B. ThreadPoolExecutor.java) nicht verfolgen konnte, da ich nicht weiß, wie man in den Source dieser Module gelangt. Aber letzlich ein wichtiger Hinweis, der mir in Zukunft nützlich sein kann !
Auch bei evtl. zukünftigen Fehlermeldungen "Permission denied" bin ich natürlich jetzt "hellsichtiger" :)
 
K

kneitzel

Gast
Also generell ist klar, dass es hier Verzögerungen geben muss. Aber nur um sicher zu gehen: Von was für Verzögerungen reden wir hier? Geht es dir um Sekunden? Oder um Millisekunden?

Generell gilt hier wie bei allen Dingen: Wenn das Laufzeitverhalten nicht ausreichend ist, dann muss zuerst analysiert werden, was für Probleme denn genau herrschen:
Wo braucht die Applikation denn wie lange?

Dann kann man überlegen, was für Schritte denn etwas bringen oder eben nichts bringen. Wenn Du a, b und c machst und das zusammen dir zu lange dauert ("Da wartet der Benutzer 10 Sekunden, dauert viel zu lange!") und dann fängst Du an, b zu optimieren. Super - ist super Optimiert - b braucht nur noch 1% der vorherigen Zeit.... Aber leider war es so, dass b vorher nur 0,1s gebraucht hat. Super optimiert ist es nur noch 0,001s - aber das Problem besteht immer noch, denn der Benutzer wartet immer noch 10s - die 0,099s Optimierung merkt er nicht!)

Hoffe es wird deutlich, was bei Optimierungen das Problem ist.
 

maksimilian

Mitglied
Ich habe jetzt noch eine grundsätzliche Frage. Ich lasse die im Code von #10 beschriebene Prozedur ExecuteRemoteCommand() im Hintergrund laufen (mit doAsync). Wie kann der Main-Thread einen Rückgabewert erhalten. mit return geht das ja dann nicht.
 

Ähnliche Java Themen

Neue Themen


Oben