Modularisierung für Raspberry im SSH Betrieb

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

Vorausgeschickt: Erste Gehversuche mit Python und brauche Hilfestellung zum Verständnis wie was zusammenspielt ;)

Ausgangssituation: Auf einem Raspberry wird ein Programm "rpiSchedule" zur Steuerung ausgeführt, es benutzt APScheduler. Der RPI läuft 24h/7d ohne Monitor und Keyboard, wir über SSH "bedient".
Die "rpiSchedule" Steuerung läuft einwandfrei, erzeugt eine print-Ausgabe über den aktuellen Zustand der Steuerung auf dem Rechner, von dem der ssh-Befehl abgesetzt wurde.
Soweit so gut ... solange der Rechner mit dem rpiSchedule gestartet wurde online ist. Natürlich stirbt die Pipe wenn der Rechner offline geht.

Fragen:
1. Welche Methode / Python Programmierung ist ratsam, um den auf dem RPI laufenden Prozess wieder monitoren zu können wenn der Rechner wieder online geht?

2. Außerdem sollte es möglich sein von einem weiteren Python Programm dem Hauptprogramm "rpiSchedule" zusätzliche Steuerungsinfos zu übergeben.

Das ganze läuft auf eine Modularisierung hinaus, wobei verschiedene Quellen APSchedule ansprechen müssen. Andererseits ein separates Programm auf die aktuellen Jobs des APSchedulers zugreifen müssen, um den aktuellen Stand zurückzugeben/Ausgabe erzeugen.

Ein paar Hinweise auf eine sinnvolle Programmstruktur und die zu verwendenden Funktionen/Module würden mir sicher weiterhelfen.

Vielen Dank
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

gNeandr hat geschrieben:1. Welche Methode / Python Programmierung ist ratsam, um den auf dem RPI laufenden Prozess wieder monitoren zu können wenn der Rechner wieder online geht?
tmux/GNU Screen?
gNeandr hat geschrieben:2. Außerdem sollte es möglich sein von einem weiteren Python Programm dem Hauptprogramm "rpiSchedule" zusätzliche Steuerungsinfos zu übergeben.
Kannst du über irgendeinen beliebigen RPC-Mechanismus machen. In Python's Stdlib ist etwa schon XML-RPC drin. Das ist zwar nicht toll, aber recht einfach zu nutzen.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

@Leonidas
Zunächst Danke für die schnelle Antwort ...

.. allerdings bin ich mit den Hinweis auf tmux etwas verloren. Die Doku (die ich gefunden habe) hilft nicht wirklich weiter. Kannst du mir ein Beispiel zeigen?
Wann / wie wird tmux eingebunden/verwendet?
BlackJack

@gNeandr: Das hat mit Python gar nichts zu tun. Wenn man ``tmux`` startet kann man zum einen mehr als ein virtuelles Terminal erstellen und zwischen denen Umschalten mit nur einer ``ssh``-Verbindung, und man kann sich von so einer Sitzung abmelden (oder die Verbindung wegbrechen lassen) ohne das die dort gestarteten Programme beendet werden. Und man kann sich auch wieder mit der Sitzung verbinden. Man kann auch mehrere Verbindung zu der selben Sitzung aufmachen.

``screen`` ist ein anderes Programm mit dem man so etwas machen kann.

Und ``byobu`` ist ein Frontend was wahlweise eines von beiden verwenden kann und ein paar Zusatzfunktionen bereit stellt.
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

@BlackJack
Danke für die Erläuterung ..
Wenn man ``tmux`` startet kann man zum einen mehr als ein virtuelles Terminal erstellen und zwischen denen Umschalten mit nur einer ``ssh``-Verbindung, und man kann sich von so einer Sitzung abmelden (oder die Verbindung wegbrechen lassen) ohne das die dort gestarteten Programme beendet werden. Und man kann sich auch wieder mit der Sitzung verbinden. Man kann auch mehrere Verbindung zu der selben Sitzung aufmachen.
... noch sehe ich nicht den Vorteil zu der Vorgehensweise mehrere Terminalfenster aufzu machen und jeweils eine ssh Verbindung aufzubauen. Wenn ich es richtig verstehe (auch nach weiteren online Recherchen) kann ich mehrere "Fenster" in einem Terminal aufmachen und dort scripts starten. Aber ist jedes "Fenster" eine selbstständige ssh-Verbindung?
Außerdem wie nehme ich eine bestimmte "session" (die zB die print Ausgabe, also Liste der anstehenden APScheduler Jobs zeigt) wieder auf, wenn mein Host Rechner zurück zum Leben kommt.

Und noch zur Klarheit: Der APScheduler läuft auf dem Raspberry. Auf dem Host läuft über ssh die print Liste auf, und die soll wieder angezeigt werden. Oder, ich möchte einen weiteren Task bzw weitere Tasks schedulen. Der soll/ die sollen dann nicht in eine neue, weitere Job Liste geschrieben werden, sondern zur bestehenden hinzugefügt werden. Das bedeutet doch, das ich "irgendwie" wieder die ursprüngliche Liste erreichen muß.

Ich hoffe, dass es verständlich beschrieben ist :wink:

PS Warum bekomme ich keinen Hinweis per Mail wenn eine neue Antwort gepostet wird, obwohl -- meine ich -- alles dafür eingerichtet ist ??
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

gNeandr hat geschrieben:
Wenn man ``tmux`` startet kann man zum einen mehr als ein virtuelles Terminal erstellen und zwischen denen Umschalten mit nur einer ``ssh``-Verbindung, und man kann sich von so einer Sitzung abmelden (oder die Verbindung wegbrechen lassen) ohne das die dort gestarteten Programme beendet werden. Und man kann sich auch wieder mit der Sitzung verbinden. Man kann auch mehrere Verbindung zu der selben Sitzung aufmachen.
... noch sehe ich nicht den Vorteil zu der Vorgehensweise mehrere Terminalfenster aufzu machen und jeweils eine ssh Verbindung aufzubauen. Wenn ich es richtig verstehe (auch nach weiteren online Recherchen) kann ich mehrere "Fenster" in einem Terminal aufmachen und dort scripts starten. Aber ist jedes "Fenster" eine selbstständige ssh-Verbindung?
Außerdem wie nehme ich eine bestimmte "session" (die zB die print Ausgabe, also Liste der anstehenden APScheduler Jobs zeigt) wieder auf, wenn mein Host Rechner zurück zum Leben kommt.
Das löst doch genau dein Problem:
  • Du verbindest dich zum Raspberry Pi mittels SSH
  • Du startest tmux
  • In tmux startest du das Python-Skript. Dieses läuft, solange tmux läuft. tmux läuft unabhängig von der SSH-Session weiter.
  • Jetzt kann beliebiges passieren: der Rechner der sich zum Pi verbunden hat geht offline, geht aus, oder verbrennt.
  • Du verbindest dich nun wieder zum Raspberry Pi via SSH. Das kann von einem beliebigen Rechner aus passieren, muss also nicht der erste sein.
  • Du verbindest dich wieder zur bestehenden tmux-Session. In dieser läuft dein Python-Skript weiter, als wäre nichts gewesen
gNeandr hat geschrieben:PS Warum bekomme ich keinen Hinweis per Mail wenn eine neue Antwort gepostet wird, obwohl -- meine ich -- alles dafür eingerichtet ist ??
Habe deine Einstellungen geprüft, die sehen korrekt aus. Der Forums-Server schickt auch korrekt Mails. Vielleicht sind sie irgendeinem Filter deinerseits zum Opfer gefallen?
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

Zunächst:
Habe deine Einstellungen geprüft, die sehen korrekt aus. Der Forums-Server schickt auch korrekt Mails. Vielleicht sind sie irgendeinem Filter deinerseits zum Opfer gefallen?
Deine Antwort wurde mir jetzt per Mail angezeigt, war vielleicht 'n Verzögerung zwischen Edit und der letzten Antwort.
Danke .. geh jetzt erst mal an deinen eigentlichen "Inhalt" ;)
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

@Leonidas

Wenn das mir tmux so funktioniert, fein; hatte ich nicht ganz verstanden, werd's mal versuchen ..

Bleibt die andere Frage, die du mit XML-RPC beantwortet hast ... sieht mir noch etwas kryptisch aus, hoffe da finde ich noch ein paar Beispiele ;)
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

gNeandr hat geschrieben:Bleibt die andere Frage, die du mit XML-RPC beantwortet hast ... sieht mir noch etwas kryptisch aus, hoffe da finde ich noch ein paar Beispiele ;)
Naja, alternativ wenn du es dir reicht einfach über die Standardeingabe irgendwie weitere Daten einzutragen dann kannst du dir auch XML-RPC sparen. Hängt halt so ein wenig von deinen Daten ab.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

Ja, so ähnlich hatte ich auch schon gedacht. Die Steuerdaten liegen als JSON oder Text/INI Datei vor. Diese werden beim Starten des rpiSchedule übergeben. Eine Möglichkeit wäre es, die Datei zu editieren, also neue/veränderte Einträge nachzutragen und damit Ändern der Datei. Der Hauptprozess prüft das timestamp der JSON/INI Datei (oder per Eventhandling) und liest diese ggf. einfach neu ein.

Schöner wäre es halt mit einem Script/Aufruf diese neuen Werte an die Verwaltung der APScheduler Jobs zu übergeben.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Also so ein Standardweg auf Unix wäre einen Signal-Handler für SIGHUP zu registrieren. SIGHUP wird von vielen Daemons verwendet um die Config neu zu laden, was bei dir etwa so der Fall wäre. Klar, du kannst auch ein Skript schreiben, aber das ist dann vermutlich mehr Aufwand :-)
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

Bei meinen Recherchen bin ich auf diese Stack Overflow Seite geraten. Dort sind zwei interessante Alternativen aufgezeigt:
-- zeromq is the way to go. Delicious, isn't it?
-- The multiprocessing library provides listeners and clients that wrap sockets and allow you to pass arbitrary python objects.

Kann jemand dazu was sagen? Erfahrungen, welche Methode ist am effizientesten?
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

Übrigens zu zeromq
... eine Beschreibung der Installation auf Ubuntu und eine Aussage zum Betrieb auf einem RaspberryPI.

GitHUB: zeromq/pyzmq Unable to install on Ubuntu #545
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

gNeandr hat geschrieben:-- zeromq is the way to go. Delicious, isn't it?
ØMQ ist nett, aber ich denke für deinen Anwendungszweck total mit Kanonen auf Spatzen geschossen. Klar, kann man machen wenn man Lust hat, man kann auch mit Protocol Buffern oder Cap'n'Proto oder Thrift sich was zusammenbauen, aber deutlich komplexer als einfach auf ein SIGHUP zu reagieren und ne Datei einzulesen. Letzteres geht geschätzt in < 10 Zeilen, für ersteres braucht man wohl mehr aufwand.
gNeandr hat geschrieben:-- The multiprocessing library provides listeners and clients that wrap sockets and allow you to pass arbitrary python objects.
Jo, man muss halt nur sicherstellen dass die Sockets nicht von unbefugten erreichbar sind, sonst können Angreifer beliebigen Code ausführen.
gNeandr hat geschrieben:Kann jemand dazu was sagen? Erfahrungen, welche Methode ist am effizientesten?
Was meinst du mit effizient? Suchst du eine Möglichkeit mehrere Millionen Messages zu verarbeiten? Dann würde ich ØMQ überlegen, aber erstmal den Rapsberry Pi durch schnellere Hardware ersetzen (also quasi alles andere, von Cubieboard, ODroid zu irgendwelchen x86-Servern). Suchst du eine flexible Möglichkeit? Dann könnte XML-RPC oder eventuell multiprocessing was für dich sein. Suchst du eine simple Möglichkeit? Dann Signals.

Edit: P.S.: ØMQ sollte auf Rasbian mit "aptitude install python-zmq" installierbar sein. Kein Kompilieren oder Installationen am Paketmanagement vorbei notwendig.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

gNeandr hat geschrieben:
-- The multiprocessing library provides listeners and clients that wrap sockets and allow you to pass arbitrary python objects.
Jo, man muss halt nur sicherstellen dass die Sockets nicht von unbefugten erreichbar sind, sonst können Angreifer beliebigen Code ausführen.
Ich denke ich werde mal diesen Weg beschreiten. Zunächst wird wohl nur 'localhost' auf dem RPI genutzt und die Datenmengen sind eher bescheiden. Das die Sockets auf dem RPI erreichbar sein sollen scheint mir eher unwahrscheinlich. Zwar liegt der RPI im HomeNetz hinter einer FBox, aber eine Angriffssitaution sehe ich nicht. Im Netz gibt's nur Linux und OSX, kein M$ ;)

Allerdings denke ich dran später den RPI/die eigentliche Anwendung aus dem lokalen Netz ansprechen zu können.

Code: Alles auswählen

address = ('localhost', 6000)
conn = Client(address, authkey='secret password')
D.h. der client Aufruf mit localhost muss dann über die ip erfolgen(?). Da muss ich mich wohl noch schlau machen.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

gNeandr hat geschrieben:Allerdings denke ich dran später den RPI/die eigentliche Anwendung aus dem lokalen Netz ansprechen zu können.

Code: Alles auswählen

address = ('localhost', 6000)
conn = Client(address, authkey='secret password')
D.h. der client Aufruf mit localhost muss dann über die ip erfolgen(?). Da muss ich mich wohl noch schlau machen.
Keine Ahnung woher der Code ist aber ich vermute dass das die Konfiguration für irgendeinen Server ist. Damit andere Hosts darauf zugreifen können, musst du statt localhost 0.0.0.0 schreiben.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

Keine Ahnung woher der Code ist aber ich vermute dass das die Konfiguration für irgendeinen Server ist. Damit andere Hosts darauf zugreifen können, musst du statt localhost 0.0.0.0 schreiben.
.. von hier, nach dem ØMQ Beispiel

Und natürlich geht's wenn ich die URL angebe:

Code: Alles auswählen

address = ('192.168.1.2', 6000)
conn = Client(address, authkey='secret password')
Und ebenso von einem anderen Rechner in lokalen Netz (mit python auf der Konsole)

Frage: Kann ich das auch per Browser machen? Also
http://192.168.1.2:6000/send?'message%20string'
Zunächst mopert der Browser mit
This address is restricted
This address uses a network port which is normally used for purposes other than Web browsing. Firefox has canceled the request for your protection.
Verständlich zumindest, da keine Authentication übertragen wurde. Aber wie wäre der richtige http string? Wenn's überhaupt geht!?

Das löst aber noch nicht die eigentliche Aufgabe: es sollen ja neue "Befehle" empfangen/verarbeitet werden (zu APScheduler Jobs) und die aktuelle Liste der Jobs soll ausgegeben werden.

Das werde ich wohl erreichen können mit der Verwendung von Threads. Dafür bin ich auf dies hier gestoßen:
Python Multithreaded Programming
Das wird jetzt richtig spannend :P
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

Hier der derzeitige "Übungsstand".
Habe jetzt multiprocessing eingesetzt und habe zwei Prozesse.

Der eine (Listener: pyListener.py) soll permanent laufen. Er hat zwei Threads, einer empfängt vom Client Prozess die erforderlichen Messages zur Steuerung und übergibt die Jobs an APScheduler. Ein zweiter Thread wird ein Protokoll mit den aktuellen Jobs ausgeben, im Beispiel einfach nur alle 2 sec ein einfaches print.

Der andere Prozess (Client: pyClient.py) sendet die Messages zur Steuerung.
Der zweite Client Prozess kann auf einem anderen Rechner liegen.

Beispiele der beiden Python Programme sind hier

Gerne höre ich Feedback ... was kann/sollte anders/besser gemacht werden. Siehe auch erstes Post:
Vorausgeschickt: Erste Gehversuche mit Python und brauche Hilfestellung zum Verständnis wie was zusammenspielt ;)
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

gNeandr hat geschrieben:Verständlich zumindest, da keine Authentication übertragen wurde. Aber wie wäre der richtige http string? Wenn's überhaupt geht!?
Wieso sollte das gehen? ØMQ ist, soweit ich das sehe kein HTTP, also wird dein Browser da Schwierigkeiten haben damit zu sprechen. XML-RPC ist über HTTP.

Den Rest schau ich mir später an, muss jetzt los…
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
BlackJack

Zum Server: Das importierte `array` wird nicht verwendet.

Auf Modulebene sollte man nur Code stehen haben der Konstanten, Funktionen und Klassen definiert und nicht das Hauptprogramm. Das verwindet normalerweise in einer `main()`-Funktion die mit folgendem Idiom aufgerufen wird:

Code: Alles auswählen

if __name__ == '__main__':
    main()
Dann kann man das Modul importieren, ohne dass das Programm los läuft, oder man kann es als Programm ausführen.

Was auf Modulebene keinesfalls stehen sollte, sind Variablen die von irgendwo mittels ``global``-Deklaration überschrieben werden. Vergiss am besten gleich dass es ``global`` überhaupt gibt. In diesem Fall kann man zum Beispiel ein `Event`-Objekt herum reichen.

Namenschreibweisen und Leerzeichen- und zeilensetzung entsprechen teilweise nicht dem Style Guide for Python Code.

Klassennamen fangen danach mit einem Grossbuchstaben an. Überhaupt: Was soll dieses unsinnige `my` bei den Namen?

`Thread`-Objekte haben schon einen Namen den man ihnen geben kann. Und was soll die ID? Grundsätzlich hat jedes Objekt bereits eine ID.

Die ”Linienkommentare” unter den ``class``-Zeilen ist sehr ungewöhnlich und stört beim lesen.

`Timer` erscheint mir kein passender Name für das was die Klasse tut und überhaupt ist eine Klasse hier überdimensioniert. Man kann mit `Thread`-Exemplaren auch Funktionen asynchron ausführen. Da braucht man keine triviale Klasse für zu schreiben.

Wenn eine ``while``-Schleife als erstes ein ``if`` enthält was die Schleife abbrechen kann, dann kann man die Bedingung auch gleich für ``while`` verwenden.

Durchnummerieren von Namen ist in der Regel ein Zeichen das man eigentlich eine Datenstruktur verwenden möchte. Oder im Fall von `thread1` und `thread2` das man die vernünftig benennen sollte, nämlich so das man am Namen erkennt was der Wert bedeutet und nicht nur das es ein Thread ist. Namen sollte man auch nicht abkürzen wenn es keine gebräuchliche Abkürzung ist. `connection` ist klarer als `conn` und `message` sicherlich weniger kryptisch als `msg`.

`time.ctime()` braucht man die aktuelle Zeit nicht zu übergeben, das ist der Default wenn nichts übergeben wird.

Einer der drei Threads ist zu viel. Warum das bedienen der Clients nicht im Hauptthread erledigen?

Beim schliessen der Verbindung und dem Setzen des Flags wäre es robuster wenn man das mit einem ``try``/``finally`` absichert.

Ich komme dann ungefähr bei so etwas heraus (ungetestet):

Code: Alles auswählen

#!/usr/bin/python
#  Listener example using two threads
#   -- listen for client messages, passing 'close' will terminate all
#   -- repeating printing time message (test purpose)
from __future__ import print_function
import time
from multiprocessing.connection import Listener
from threading import Event, Thread


def print_time(exit_event, name, delay):
    print('Starting', name)
    try:
        while not exit_event.is_set():
            time.sleep(delay)
            print('{0}: {1} {2}'.format(name, time.ctime(), delay))
    finally:
        print('Exiting', name)


def serve(exit_event, name):
    address = ('192.168.1.2', 6000)
    listener = Listener(address, authkey='secret password')
    try:
        while True:
            connection = listener.accept()
            message = connection.recv()
            if message == 'close':
                connection.close()
                print(
                    '{0}: connection closed from {1}'.format(
                        name, listener.last_accepted
                    )
                )
                break
            # 
            # process the incoming message...
            # 
            print(
                '{0}: connection accepted {1} {2}'.format(
                    name, listener.last_accepted, message
                )
            )
    finally:
        exit_event.set()
        listener.close()


def main():
    exit_event = Event()
    Thread(target=print_time, args=(exit_event, 'myTimer', 2)).start()
    serve(exit_event, 'myListener')
    print('Exiting Main Thread')


if __name__ == '__main__':
    main()
Antworten