Endlosprogramm - Ausführung beenden lassen

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
tlosert
User
Beiträge: 18
Registriert: Donnerstag 6. Mai 2021, 13:05

Hallo zusammen,

zunächst muss mich erst mal als ein Python-Neuling auten :D
Ich habe ein kleines Programm angepasst, in dem ich diverse Aktionen ausführen lassen möchte, sobald ich bei Telegram im Bot gewisse "Befehle" schreibe.
Also gebe ich z.B. Test ein, erscheint im Bot (testweise) die Nachricht "oh du verstehst mich". Soweit so gut.
Nun möchte ich per Befehl das "Endlos-Programm" beenden lassen.
Bei der Eingabe von "Ende" soll das Programm stoppen. Habe schon diverse Sachen wie sys.exit(), raise SystemExit ausprobiert, aber es läuft immer weiter.

Anbei das tolle Programm:

Code: Alles auswählen

import sys
import time
import telepot
import os
from telepot.loop import MessageLoop

def handle(msg):
    content_type, chat_type, chat_id = telepot.glance(msg)
    print(content_type, chat_type, chat_id)

    if content_type == 'text':
        bot.sendMessage(chat_id, msg['text'])
        if msg['text'] == 'Test':
          bot.sendMessage(chat_id, 'oh du verstehst mich')
        elif msg['text'] == 'Start':
          bot.sendMessage(chat_id, 'was soll denn gestartet werden?')
       [b][color=#FF0000] elif msg['text'] == 'Ende':
          bot.sendMessage(chat_id, 'Programm wird beendet')
          exit()[/color][/b]
        else: bot.sendMessage(chat_id, 'keine Ahnung was du möchtest')
          
    print (chat_id)
    print (bot)


bot = telepot.Bot('X64xxxxxxx:AxxxxxPMQexxxxxr5xxxGjxxxxTtixxxxhhxxx')
MessageLoop(bot, handle).run_as_thread()
print ('Listening ...')

# Keep the program running.
while 1:
    time.sleep(10)
Was muss ich ändern?

Danke schon mal und Grüße
Thomas
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@tlosert: Wenn man in den Code schaut startet der diverse Threads und in jedem läuft eine Endlosschleife die alle Ausnahmen schluckt und weiterläuft. Yeah. Wer sich das ausgedacht hat, für den gibt es einen speziellen Platz in der Hölle. Da muss man ziemlich hart aussteigen, weil sauber kommt man aus der Nummer nicht raus.

Anmerkungen zum Quelltext: Eingerückt wird vier Leerzeichen pro Ebene.

`exit()` gibt es eigentlich nicht einfach so, das muss man aus `sys` importieren. Einfach so existiert das nur zufällig und ist undokumentiert.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Das hat zur Folge das `bot` nicht mehr so einfach magisch überall vorhanden ist. Funktionen und Methoden bekommen alles was sie ausser Konstanten benötigen, als Argument(e) übergeben.

Statt die `MessageLoop` in einem eigenen Thread zu starten und im Hauptthread dann in einer sinnlosen Warteschleife zu ”idlen”, sollte man die `MessageLoop` einfach im Hauptthread laufen lassen.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import os
import signal
from functools import partial

import telepot
from telepot.loop import MessageLoop


def handle(bot, message):
    content_type, chat_type, chat_id = telepot.glance(message)
    print(content_type, chat_type, chat_id)

    if content_type == "text":
        text = message["text"]
        bot.sendMessage(chat_id, text)

        if text == "Test":
            bot.sendMessage(chat_id, "oh du verstehst mich")

        elif text == "Start":
            bot.sendMessage(chat_id, "was soll denn gestartet werden?")

        elif text == "Ende":
            bot.sendMessage(chat_id, "Programm wird beendet")
            os.kill(os.getpid(), signal.SIGTERM)

        else:
            bot.sendMessage(chat_id, "keine Ahnung was du möchtest")


def main():
    bot = telepot.Bot("X64xxxxxxx:AxxxxxPMQexxxxxr5xxxGjxxxxTtixxxxhhxxx")
    print("Listening ...")
    MessageLoop(bot, partial(handle, bot)).run_forever()


if __name__ == "__main__":
    main()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
tlosert
User
Beiträge: 18
Registriert: Donnerstag 6. Mai 2021, 13:05

__blackjack__ hat geschrieben: Donnerstag 6. Mai 2021, 22:07 @tlosert: Wenn man in den Code schaut startet der diverse Threads und in jedem läuft eine Endlosschleife die alle Ausnahmen schluckt und weiterläuft. Yeah. Wer sich das ausgedacht hat, für den gibt es einen speziellen Platz in der Hölle. Da muss man ziemlich hart aussteigen, weil sauber kommt man aus der Nummer nicht raus.

Anmerkungen zum Quelltext: Eingerückt wird vier Leerzeichen pro Ebene.

`exit()` gibt es eigentlich nicht einfach so, das muss man aus `sys` importieren. Einfach so existiert das nur zufällig und ist undokumentiert.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Das hat zur Folge das `bot` nicht mehr so einfach magisch überall vorhanden ist. Funktionen und Methoden bekommen alles was sie ausser Konstanten benötigen, als Argument(e) übergeben.

Statt die `MessageLoop` in einem eigenen Thread zu starten und im Hauptthread dann in einer sinnlosen Warteschleife zu ”idlen”, sollte man die `MessageLoop` einfach im Hauptthread laufen lassen.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import os
import signal
from functools import partial

import telepot
from telepot.loop import MessageLoop


def handle(bot, message):
    content_type, chat_type, chat_id = telepot.glance(message)
    print(content_type, chat_type, chat_id)

    if content_type == "text":
        text = message["text"]
        bot.sendMessage(chat_id, text)

        if text == "Test":
            bot.sendMessage(chat_id, "oh du verstehst mich")

        elif text == "Start":
            bot.sendMessage(chat_id, "was soll denn gestartet werden?")

        elif text == "Ende":
            bot.sendMessage(chat_id, "Programm wird beendet")
            os.kill(os.getpid(), signal.SIGTERM)

        else:
            bot.sendMessage(chat_id, "keine Ahnung was du möchtest")


def main():
    bot = telepot.Bot("X64xxxxxxx:AxxxxxPMQexxxxxr5xxxGjxxxxTtixxxxhhxxx")
    print("Listening ...")
    MessageLoop(bot, partial(handle, bot)).run_forever()


if __name__ == "__main__":
    main()
Guten Abend,

vielen Dank für die angepasste Version. Ich hatte diese schon ausprobiert, das Programm lässt sich beenden.
ABER beim erneuten Start bricht es gleich wieder ab, als ob "Ende" in den Bot als Befehl eingegeben worden war.

Es erscheint folgendes bei jedem neuen Programmstart:

Code: Alles auswählen

Backend terminated or disconnected. Use 'Stop/Restart' to restart.
Grüße
Thomas
Benutzeravatar
Dennis89
User
Beiträge: 1124
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Morgen,

@__blackjack__ ich habe zu deinem Programm auch eine kleine Zwischenfrage. Ich kann nicht erkennen wie/wo 'message' in die Funktion 'handle' kommt? Nach meinem Verständnis wird nur 'bot' beim Aufruf übergeben.
Ich habe allerdings noch nie etwas mit 'telepot' gemacht, eventuell verstehe ich es auch deswegen nicht.

Danke und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Wie startest Du welches Programm? Etwa aus einer IDE heraus, die den Python-Prozess nicht sauber trennt?

@Dennis86: `handle` ist ein Callback, der vom MessageLoop mit dem Argument message aufgerufen wird. Das partial braucht man, weil man zusätzlich noch weitere Argumente braucht, die MessageLoop beim Aufruf nicht übergibt.
Benutzeravatar
Dennis89
User
Beiträge: 1124
Registriert: Freitag 11. Dezember 2020, 15:13

Danke @Sirius3 🙂

Grüße Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
tlosert
User
Beiträge: 18
Registriert: Donnerstag 6. Mai 2021, 13:05

Sirius3 hat geschrieben: Dienstag 18. Mai 2021, 06:29 Wie startest Du welches Programm? Etwa aus einer IDE heraus, die den Python-Prozess nicht sauber trennt?

@Dennis86: `handle` ist ein Callback, der vom MessageLoop mit dem Argument message aufgerufen wird. Das partial braucht man, weil man zusätzlich noch weitere Argumente braucht, die MessageLoop beim Aufruf nicht übergibt.
Also ich habe einmal in der crontab "@reboot python3 /home/pi/telegram/bot.py" stehen. Nach Beendigung wollte ich es einfach per Smartphone App mit ssh Zugriff auf den pi mit "python3 /home/pi/telegram/bot.py &" erneut starten.
Wenn es keine andere Lösung gibt würde ich das Programm so umstellen, dass es nur einmal durchläuft (kein loop) und das dann je Minute. Ist ja nur Bastelei aber macht Spaß...
Grüße Thomas
tlosert
User
Beiträge: 18
Registriert: Donnerstag 6. Mai 2021, 13:05

Guten Morgen,
habe einiges probiert, aber leider nicht es hinbekommen mit dem Beenden des Programmes. Bzw es wird ja beendet aber beim nächsten Start geht es sofort in das neue ende. Besteht denn die Möglichkeit diese Schleife 10 Sekunden laufen zu lassen und dann das Programm automatisch beenden? So könnte ich es einfach jede zwei Minuten einmal starten und dann wird auch die Telegramm Queue abgearbeitet? Grüße Thomas
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Warum muss das beendet werden? Dafür ist Telegram nicht gemacht. Und im Zweifel verlierst du Nachrichten.
tlosert
User
Beiträge: 18
Registriert: Donnerstag 6. Mai 2021, 13:05

__deets__ hat geschrieben: Mittwoch 19. Mai 2021, 08:03 Warum muss das beendet werden? Dafür ist Telegram nicht gemacht. Und im Zweifel verlierst du Nachrichten.
Na ja das aktuelle Beenden beendet auch gleich jeden Neustart mit, warum auch immer. Zur Not, da es nur eine kleine bot Spielerei von mir ist kann das Programm schauen welche Nachrichten geschickt worden sind und dann entsprechend 10 Sekunden lang reagieren.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@tlosert: das beantwortet noch nicht die Frage, warum Du im ersten Schritt überhaupt Deinen Bot beenden möchtest.
tlosert
User
Beiträge: 18
Registriert: Donnerstag 6. Mai 2021, 13:05

Sirius3 hat geschrieben: Mittwoch 19. Mai 2021, 14:00 @tlosert: das beantwortet noch nicht die Frage, warum Du im ersten Schritt überhaupt Deinen Bot beenden möchtest.
Ja stimmt, eigentlich ist es egal. Dachte halt wenn ich was starte, kann ich es auch beenden. Aber im Prinzip kann es ja durchlaufen.
Damit bin ich aber auf ein neues Problem gestoßen, beschreibe es gleich mal hier.
tlosert
User
Beiträge: 18
Registriert: Donnerstag 6. Mai 2021, 13:05

Also, folgende Tatsache besteht:
Starte ich das Programm in Thonny, läuft es reibungslos. Die bisher in Telegramm eingegeben Kommandos, etc. werden der Reihe nach abgearbeitet, es sieht wie folgt aus (Text in der Shell):

Code: Alles auswählen

Python 3.7.3 (/usr/bin/python3)
>>> %Run bot.py
Listening ...
>>> 635555555
<telepot.Bot object at 0xb5fffff0>
635555555
<telepot.Bot object at 0xb5fffff0>
635555555
<telepot.Bot object at 0xb5fffff0>
635555555
<telepot.Bot object at 0xb5fffff0>
635555555
<telepot.Bot object at 0xb5fffff0>
Aber, starte ich das Programm im Terminal oder via crontab mit: "

Code: Alles auswählen

pi@raspberrypi:~/thomas/telegram $ /usr/bin/python3 bot.py
" wird mir zwar "Listening ..." angezeigt, was im Programm via

Code: Alles auswählen

print 
ausgegeben wird. Es tut sich aber nichts weiteres, d.h. gebe ich im Bot auf dem Smartphone Befehle ein, passiert nichts. Also läuft das Programm bisher nur, wenn ich es unter Thonny starte. Das verstehe ich nicht.

Starte ich python3 mit sudo, bekomme ich sogar folgenden Fehler angezeigt:

Code: Alles auswählen

pi@raspberrypi:~/thomas/telegram $ sudo python3 bot.py
Traceback (most recent call last):
  File "bot.py", line 4, in <module>
    import telepot
ModuleNotFoundError: No module named 'telepot'
Ist das Modul "telepot" dann ggf. nur für den user pi installiert? k.a.?

Danke für eure Unterstützung. Alles wird irgendwann gut :-)
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wie sieht dein Programm *jetzt* aus? Hast du Sirius Anregungen übernommen? Ich vermute mal nicht, und du erlebst jetzt, was du schon immer erleben wolltest: dein Programm beendet sich. Und der Thread im Hintergrund, der sonst von Thonny am Leben gehalten wurde, beendet sich.

Benutz Sirius3’s Grundlagen.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Warum startest Du jetzt mit sudo? Wenn Du telebot lokal installiert hast, dann kennt das root nicht.
tlosert
User
Beiträge: 18
Registriert: Donnerstag 6. Mai 2021, 13:05

Sirius3 hat geschrieben: Donnerstag 20. Mai 2021, 10:20 Warum startest Du jetzt mit sudo? Wenn Du telebot lokal installiert hast, dann kennt das root nicht.
Das war nur ein Versuch, da ich mich noch nicht so gut auskenne. Klingt aber logisch die Erklärung.
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

tlosert hat geschrieben: Dienstag 18. Mai 2021, 09:03 Also ich habe einmal in der crontab "@reboot python3 /home/pi/telegram/bot.py" stehen.
So ein Linux-System hat eigens Möglichkeiten zum Management von Hintergrundprozessen (auch Daemons genannt), und ein aktuelles Raspbian nutzt dafür wie die meisten anderen Distributionen eine Software namens systemd. Dazu mußt Du nur ein sogenanntes Unitfile schreiben, es an die richtige Stelle (zum Beispiel /etc/systemd/system/) legen und es Deinem systemd-System einmalig (oder nach jeder Änderung Deines Unitfile) mit dem Kommandozeilenbefehl mit "systemctl daemon-reload" bekannt machen. Danach kannst Du die Ausführung Deines Programms mit "systemctl [Befehl] [Unitfilename]" steuern, wobei [Unitfilename] für den eindeutigen Namen Deines Unitfile steht und [Befehl] etwa "start", "stop", "enable" und "disable" sein kann. Was die ersten beiden tun, dürfte klar sein, die Befehle "enable" und "disable" werden benutzt, damit Dein Unitfile beim Booten automatisch gestartet wird ("enable") oder eben nicht ("disable"). Einen Einstieg findest Du hier: [1, 2]. Neben dem Vorteil der einheitlichen Verwaltung Deiner Hintergrundprogramme bindet systemd Dein Programm automatisch ins systemweite Logging ein; alles, was Dein Programm auf STDOUT und STDERR (Python: sys.stdout und sys.stderr) ausgibt, landet in den systemweiten Logdateien, inklusive Zeitstempel und allem drum und dran. systemd kann Dein Programm zusätzlich überwachen und es neu starten, wenn es abgestürzt ist oder (zum Beispiel vom OOM-Killer) beendet wurde.

Der zweite Punkt, den ich persönlich nicht so schön finde, ist, daß Dein Programm in Deinem Homeverzeichnis liegt. Zur Entwicklungszeit und auf einem privaten System ist das natürlich kein Problem, aber wenn Du damit in einen dauerhaften Betrieb gehen und vielleicht auch noch andere Systemadministratoren dabeihaben möchtest, ist es eleganter, Dein Programm dort zu installieren, wo selbstgestrickte Programme unter einem Linux nun einmal hingehören, nämlich in der Ordnerhierachie /usr/local/, in Deinem Fall also /usr/local/bin/.


[1] https://www.netways.de/blog/2017/10/13/ ... unitfiles/
[2] https://wiki.ubuntuusers.de/systemd/Units/
Antworten