Posts by Boris-Gaertner

    Es freut mich außerordentlich, diese Erfolgemeldung zu lesen. Die in der Nachricht enthaltene Danksagung weiß ich natürlich zu schätzen.


    Ob Du den Code ins Forum kopieren willst, ist allein deine Entcheidung. Es könnte schon sein, dass das nochmal jemanden interessiert; schließlich ist das Thema ja reizvoll und die Hardware für Roboterautos seit einiger Zeit auch recht preiswert.


    Du solltest bitte auch überlegen, das Thema nun abzuschliessen; das würde die Forumsleiter freuen, die eine große Zahl offener Themen oft beunruhigend finden.

    Das sudo apt-get dist-upgrade nicht funktioniert, ist bei einem so alten Ausgangssystem wie Wheezy nicht überraschend. Es bleibt also nur, RaspberryOS (das basiert auf Debian Buster, ist also das aktuellste, was es in der Ecke gegenwärtig gibt) herunterzuladen, auf eine SD-Karte zu übertragen und mit dieser Karte neu anzufangen. Neu anfangen heißt natürlich, alles nachzuinstallieren, was in dem alten System zusätzlich zum Grundsystem installiert wurde.


    Sinnvoll ist es auch, im alten Betriebssystem das Heimatverzeichnis aller Benutzer in eine Archivdatei zu verpacken und zu sichern. Im Heimatverzeichnis können sich erhaltenswerte Dateien befinden, die nicht durch Nachinstallation zusätzlicher Pakete wiederhergestellt werden können. Vermutlich gibt es auf dem alten System nur den einen Benutzer pi; das Heimatverzeichnis dieses Benutzers ist natürlich /home/pi.

    Es wäre ja schön, wenn dieser Münzkassierer wenigstens eine Typenbezeichnung hätte, aber die Angabe einer Typenbezeichnung ist bei Aliexpress wohl ebenso unüblich wie die Bereitstellung des Datenblatts. Das ist bei chinesischen Anbietern immer der gleiche Ärger; die haben den Elektronikhandel auf ein Niveau heruntergezogen, das man vor 30 Jahren noch als völlig unzumutbar abgelehnt hätte.


    Vielleicht könntest Du dir mal die folgenden Seiten angucken und prüfen, ob dein Gerät den dort besprochenen Geräten wenigstens ähnlich sieht:


    Münzkassierer mit Arduino

    Verwendung der CH92x-Münzkassierer

    Datenblatt Münzkassierer DG600F


    Vor allem der zweite Link ist im Prinzip sehr gehaltvoll, tatsächlich aber nur dann, wenn er auf dein Gerät passt. Der dritte Link beschreibt ein etwas anderes Gerät, das ich nicht auf Aliexpress, sondern bei einem europäischen Anbieter gefunden habe.


    Es scheint, dass diese Münzkassierer zwei Betriebsmodi haben:

    • Der Trainingsmodus (was Du wohl "anlernen" nennst), in dem dem Gerät die verschiedenen Münzsorten bekanntgemacht werden.
    • Der Gebrauchsmodus, in dem das Gerät in der Lage ist, eingeworfene Münzen zu erkennen

    Es scheint, das die CH92x-Geräte im Gebrauchsmodus für jede eingeworfene Münze eine Impulsfolge über die Leitung COIN schicken, die den Münzwert mitteilt. Es scheint weiterhin, dass einige Geräte auch in der Lage sind, über eine RS232-Schnittstelle zu kommunizieren, aber dies scheint nicht durchwegs der Fall zu sein. Hier wäre es also sehr wichtig, zu wissen, was dein Gerät kann und was es nicht kann.

    Die wesentliche Dokumentation ist die folgende: Programmierschnittstelle Distanzsensor sowie Quellcode. Der zuletzt angegebene Link erscheint mir besonders ergiebig, obwohl das Verständnis des Quellcodes natürlich einiges Einfühlungsvermögen verlangt. Nach meinem Verständnis des Quellcodes verwenden die meisten Eingabesensoren (nämlich alle, die von SmoothedInputDevice abgeleitet sind, da gehört DistanceSensor dazu) eine Queue, in der sie die eine bestimmte Anzahl der zuletzt gemessenen Werte speichern. Bei der Berechnung der Distanz werden dann Mittelwerte gebildet. Diese Mittelwertbildung führt wohl dazu, dass das Unterschreiten eines Mindestabstands erst mit einiger Verzögung festgestellt wird, wenn der Abstand mit der Methode distance abgefragt wird.


    Eine Queue (der deutsche Ausdruck ist "Warteschlange") ist eine Datenstruktur, die eine feste Zahl von Werten speichert und die so verwendet wird, dass immer die zuletzt erhaltenen Werte gespeichert sind. Wenn eine Queue bereits voll ist und ein weiterer Wert gespeichert werden soll, wird zunächst der älteste Wert gelöscht, um Platz für den neuesten Wert zu schaffen.


    Im Zusammenhang mit Eingabesensoren werden Queues verwendet, um Mittelwerte über die zuletzt erhaltenen Meßwerte binden zu können. Von der Mittelwertbildung erhofft man sich den Ausgleich von Meßungenauigkeiten und zufälligen Meßfehlern.


    Die Länge der Queue für den Distanzsensor wird in der Dokumentation mit 30 angegeben; im oben angegeben Link "Quellcode" steht aber der Wert 9. Welcher Wert stimmt, weiß ich nicht.


    Die Distanzsensoren messen einmal in 60 ms (also einmal in 0.06 s), dieser Wert wird vom Hersteller des Sensors empfohlen, damit einem Echo ausreichend Zeit zum Eintreffen bleibt, bevor der nächste Meßton ausgesendet wird. 30 Messungen verlangen also 1,8 Sekunden, nach dieser Zeit ist die Queue mit Werten gefüllt. Der gemittelte Wert, der durch die Methode distance geliefert wird, ist also ein Mittelwert aus einem Zeitfenster von 1,8 Sekunden.


    Man kann die Länge der Queue einstellen, dazu gibt man bei der Erzeugung des Sensorobjekts das zusätzliche Agrument queue_len an. Statt

    Python
    ultraschall_rechts = DistanceSensor(echo=23, trigger=22)

    ist also zu schreiben:


    Python
    ultraschall_rechts = DistanceSensor(echo=23, trigger=22, queue_len=6)



    Mit einer Queue-Länge von 6 werden nur noch 6 Meßwerte berücksichtigt; die Methode distance bildet die Werte dann über einem Zeitfenster von 6*0.06 = 0.36s. Es könnte also sein, dass wir soeine raschere Reaktion erhalten. Allerdings wird das System dann auch empfindlicher gegenüber Meßfehlern, weil ja die Mittelwertbildung nicht mehr so stark ausgleichen kann, wenn nur noch wenige Werte in die Rechnung eingehen.


    Mein Vorschlag: Die Distanzsensoren versuchsweise mit dem zusätzlichen Argument queue_len herstellen und für dieses zusätzliche Argument verschiedene kleine Werte (z.B. 6, 9, 12, 15) auspobieren. Das sollte natürlich für alle vier Sensoren geschehen, die immer Queues gleicher Länge haben sollen.


    Es wäre interessant, das Ergebnis dieser Versuche zu erfahren.


    ==============================================================


    Noch eine Bemerkung zum Code, die mit dem wahrgenommenen Problem der langsamen Reaktion nichts zu tun hat: Die Anweisungen in den Zeilen 70 und 62 können weggelassen werden, da sie Zufallswerte erzeugen, die nicht verwendet werden. Die Aufrufe von

    Code
    if random.choice([1,2]) == 1:


    in den Zeilen 71 und 63, die ja tatsächlich in if-Anweisungen verwendet werden, sind ausreichend.

    (Das ist natürlich eine ziemlich pingelige Bemerkung, aber ich bin es gewöhnt, auf solche Dinge zu achten. Das ist unvermeidlich, wenn man sein Leben in der Programmierung zugebracht hat. Sorry.)

    Es gibt verschiedene Möglichkeiten, Python-Programme über ein Terminalfenster zu starten. Einmal

    Bash
    python3 datei.py


    Als anderes Verfahren bietet sich an, in der ersten Zeile der Programmdatei zu schreiben:

    Python
    #!/usr/bin/env python3

    Sodann der Datei das Ausführungsrecht zu geben mit

    Bash
    chmod u+x datei.py

    und das Programm im Terminalfenster zu starten mit

    Bash
    ./datei.py


    Welche dieser Verfahren funktionieren bei dir nicht?

    Beziehungsweise einfach ein Betriebssystem gleichzeitig mit 2 Pi ansteuern zu können?

    Das geht nicht. Auch wenn man mehrere Raspberries in einem Netzwerk miteinander verbindet, braucht jeder Raspberry sein Betriebssystem. Das Betriebssystem managt die Benutzung genau eines Raspberry vom Start bis zum Herunterfahren und ermöglicht die Nutzung der Resourcen dieses einen Raspberry. Zwei Raspberries können nur über das Netzwerk miteinander verbunden werden und das auch nur, weil auf beiden Raspberries die Netzwerkdienste durch die jeweiligen Betriebssysteme bereitgestellt werden. In den Nachrichtenaustausch zwischen zwei Raspberries sind immer auch zwei Betriebssysteme involviert.

    Ich hatte gedacht dass man zumindest den Ram-Speicher zusammenlegen könnte um das Gerät leistungsfähiger zu machen?

    Wenn "Ram-Speicher zusammenlegen" bedeuten soll, dass jeder der beiden Raspberries dann unmittelbaren Zugriff auf 16 GB Speicher hat und diese 16 GB Speicher die gemeinsame Resource beider Rechner sind, dann muss klar gesagt werden, dass das nicht möglich ist. Jeder Raspberry hat seinen Speicher, an den er keinen anderen Raspberry ranlassen kann. Zwei miteinander verbundene Raspberries nennt man ein Mehrrechnersystem ohne gemeinsamen Arbeitsspeicher.


    Wenn man zwei Raspberries über einen Router (oder über einen Switch) miteinander verbindet, können sie über diese Verbindung miteinander kooperieren. Kooperieren heißt, dass sie Daten und, falls gewünscht, auch Programme miteinander austauschen können. Es muss aber wirklich jedes einzelne Byte, das von einem Rechner an den anderen gehen soll, über den Router übertragen werden, weil es keinen anderen Übertragungsweg gibt. Diese Kommunikation ist beim Austausch größerer Datenmengen ein Flaschenhals, weil ein Netzwerk deutlich langsamer ist als ein Prozessor. Außerdem muss die Kommunikation zwischen zwei Rechnern entweder individuell programmiert oder durch geeignete Hilfsprogramme organisiert werden. Ein mögliches Hilfsprogramm ist NFS (Network File System), das im Repository von RaspberryOS zur Verfügung steht (allerdings auf allen teilnehmenden Rechnern installiert werden muss). NFS ermöglicht einem Rechner den Zugriff auf Dateien eines anderen im Netzwerk erreichbaren Rechners. Außerdem kann man mit NFS auch Dateien an miteinander verbundene Rechner verteilen.


    Zwei Rechner miteinander zu verbinden, finde ich durchaus reizvoll; sie dann auch sinnvoll zu beschäftigen ist aber ziemlich herausfordernd. Ich halte es für utopisch, durch die Verbindung von zwei Rechnern eine Leistungsverdoppelung zu erzielen. Ein Performancezugewinn von 65 % bei der Bearbeitung von Aufgaben, die sich gut auf zwei Rechner verteilen lassen, würde ich einen sehr beeindruckenden Erfolg nennen. Das Problem ist, dass man geeignete Aufgaben erst einmal finden muss. Da sind Physiker und Berechnungsingenieure sehr erfolgreich - alle anderen kommen mit so wenig Mathematik durchs (Berufs-)Leben, dass sie nur ausnahmsweise fündig werden. Ich finde es reizvoll, Fraktale zu berechnen, aber ich weiß selbstverständlich, dass das nicht allen als sinnvolle Aufgabe gilt.


    Die große Nummer der Informatiker sind natürlich die Suchmaschinen, denen eine eigentlich sehr einfache Idee zugrunde liegt: Wenn man so viele Daten hat, dass man sie auf mehrere Rechner verteilen muss, dann kann man auch alle Rechner in die Suche mit einbeziehen. Das Prinzip ist, dass jeder seinen Datenbestand durchsucht und zum Schluss alle ihre Ergebnisse zusammenwerfen.


    In der praktischen Mathematik kann man nach Rechenaufgaben suchen, die sich in Teilaufgaben zerlegen lassen, die voneinander weitgehend unabhängig sind. Weitgehend unabhängig soll heißen, dass die beteiligten Rechner wesentliche Rechenleistung jeder für sich erbringen, gelegentlich Zwischenergebnisse austauschen und dann wieder jeder für sich weiterrechnen. Dieses Schema ist vielversprechend, wenn der Kommunikationsaufwand deutlich unter dem Rechenaufwand liegt. Wenn man mit großen Matrizen rechnen muss, könnte das klappen.

    Wenn 2 Raspberries miteinander kooperieren sollen, würde ich ihnen nichtöffentliche statische IP-Adressen außerhalb des von DHCP verwalteten Adresspools geben und sie an den gleichen Router hängen. Das genügt grundsätzlich, um sie miteinander kommunizieren zu lassen. Um von einem Raspberry auf den anderen zuzugreifen, genügt telnet - ein zugegebenermaßen sehr altes Programm. Die vorgeschlagene Konfiguration erlaubt es auch, die für die Parallelprogrammierung vorgesehenen Hilfmittel von Mathematica zu verwenden, was natürlich eine gewisse Affinität zu Mathematica voraussetzt. Vor Jahren habe ich mit der angesprochenen Konfigration mal MPI (Message Passing Interface) ausprobiert, das hat funktioniert, aber ich habe dann keine Zeit gefunden, viel damit zu machen. Eigentlich wollte ich damit verteilte numerische Mathematik machen, aber das steht immer noch auf meiner Wunschliste.


    Für MPI muss auf allen beteiligten Rechnern mpi-default-bin installiert sein; dazu auf dem Rechner, auf dem Programme compiliert werden sollen, die MPI verwenden, zusätzlich mpi-default-dev.


    Nebenbemerkung: Der Raspberry PI 4 hat ja ebenso wie einige Vorgängermodelle 4 Kerne. Wenn es um numerische Mathematik geht, könnte es auch attraktiv sein, diese vier Kerne eines Raspberries tatsächlich zu verwenden (auch da kann man, wenn ich mich recht erinnere, MPI für nutzen), das erspart die Kommunikation über TPC/IP.

    Mit dem Spannungswandler COM-KY051VT habe ich keine Erfahrung, aber hier ist ein Beschaltungsvorschlag für dieses Bauteil und den Untraschallsensor.


    Interessant finde ich auch diesen Beitrag, bei dem 4 Ultraschallsensoren an einen Raspberry angeschlossen werden sollen, allerdings ohne Spannungswandler, sondern mit Spannungsteilern, die mit Widerständen aufgebaut wurden. Leider steht in dem Beitrag nicht, ob das Vorhaben erfolgreich war. Wichtig finde ich in diesem Beitrag den Hinweis, dass sich Ultraschallsensoren unter ungünstigen Umständen gegenseitig stören können, wenn etwa ein Sensor statt eines direkten Echos einen Ton aufnimmt, der mehrfach reflektiert wurde und folglich bei der Datenauswertung einen großen Abstandwert gibt. Ob dieser Effekt tatsächlich auftritt, wenn die 4 Sensoren in unterschiedliche Richtungen messen, wird sich in der Praxis zeigen. Unter Umständen wird es nötig sein, die Sensoren mit ausreichendem zeitlichem Abstand zu triggern.

    Dieses Motor_stop() ist zu viel. Dann dreht er sich gar nicht mehr. Sobald er wieder ausser Reichweite ist, fährt er selber wieder vorwärts.

    Hier habe ich jetzt einen Augenblick überlegen müssen, um zu verstehen, warum das so ist. Mein Tipp war in diesem Punkt also falsch. Da siehst Du, dass auch Leute, die in der Programmierung alt geworden sind, noch Denkfehler machen. That's life.

    Die Funktion random.choice wird verwendet, um eine zufällige Auswahl aus einer Aufzählung von Dingen zu erhalten. Dabei entscheidet der Programmierer selbst, aus was für Dingen er auswählen will. Das macht die Auswahl sehr vielseitig; kann aber auch zu Verständnisproblemen führen. Fangen wir ganz einfach an (zunächst ohne direkten Bezug zum Roboterauto):

    Python
        zufallswert = random.choice(["Kopf", "Zahl"])

    Hier wird aus den Zeichenreihen "Kopf", "Zahl" eine zufällig gewählt. Die Auswahl ist auch das Ergebnis des Funktionsaufrufs. Wenn Du die gezeigte Anweisung in einer Schleife 10 mal ausführst, hast du gute Chancen, "kopf" und "Zahl" ungefähr gleich oft zu erhalten. Gleiche Häufigkeit (5 zu 5) ist aber nicht garantiert.

    Dieses Beispiel ist natürlich das bekannte Münzenwerfen - eben nach Python übersetzt.


    Ein anderes bekanntes Zufallsspiel - das Knobeln - kann man so schreiben:

    Python
       zufallswert = random.choice(["Stein", "Schere", "Papier"])


    Wenn schon von Spielen die Rede ist, in denen der Zufall eine Rolle spielt, darf natürlich der für Brettspiele unentbehrliche Würfel nicht fehlen. Hier ist er:

    Code
       zufallswert = random.choice([1, 2, 3, 4, 5, 6])

    Hier wählt die Funktion random.choice aus den Zahlen von 1 bis 6 eine zufällig aus; jede Ausführung des Ausdrucks liefert hier eine Zahl. Das ist ein Unterschied zu den beiden anderen Beispielen, bei denen das Ergebnis der Auswahl eine Zeichenreihe war.


    Die schon mehrfach erörterte Anweisung

    Python
        random.choice([1, 2])

    liefert eine der Zahlen 1, 2 in zufälliger Auswahl. Man könnte sagen, dass hier die Beispiele "Würfel" und "Kopf oder Zahl" kombiniert sind: Vom Würfelbeispiel kommt die Verwendung von Zahlen, vom Kopf-Zahl-Beispiel die Auswahl zwischen nur zwei Werten.


    In

    Code
       random.choice([motor_left, motor_right])

    wird eine von zwei im Programm vereinbarten Funktionen zufällig ausgewählt - das ist Python für Fortgeschrittene. Es ist vielleicht besser, wenn Du dich zunächst an den Vorschlag im Beitrag #109 hältst, also eine der Zahlen 1, 2 zufällig ziehst und in einer mit if und else programmierten Fallunterscheidung entscheidest, welcher Motor nun eine Drehbewegung verursachen soll.


    Zu der letzten Frage:

    Code
    1 == motor_left()
    2 == motor_right()

    Das sind zwei Vergleichsanweisungen:

    In der ersten Zeile wird geprüft, ob das Ergebnis des Aufrufs motor_left() der Zahl 1 gleich ist; das Ergebnis des Vergleichs wird aber weder genutzt noch gespeichert, sondern einfach weggeworfen.

    In gleicher Weise wird in der zweiten Zeile geprüft, ob das Ergebnis des Aufrifs motor_right() der Zahl 2 gleich ist; das Ergebnis des Vergleichs wird ebenfalls weggeworfen.


    Diese beiden Anweisungen machen also überhaupt nicht das, was Du wohl erwartet hast. Dass Du trotzdem keine Fehlermeldungen von Python bekommst, liegt daran, dass beide Anweisungen fehlerfrei sind - bloss eben etwas ganz anderes machen als Du erwartest. Wenn dieser Effekt auftritt - und er tritt in Python oft auf - hat man ein Problem: Wenn Anweisungen, die nicht das Erwartete tun, nach den Regeln der Programmiersprache fehlerfrei sind, gibt es von Seiten der Programmiersprache keinerlei Hilfe bei der Fehlersuche. Man muss dann selbst herausfinden, was falsch gelaufen ist.


    Hier wurden keine Variablen erzeugt! Wenn Du Variablen erzeugen willst, musst Du daran denken, dass eine Variable mit einem Buchstaben beginnt. Also so:


    Code
    variable1 = motor_left()

    Beachte hier auch, dass die Wertzuweisung an eine Variable mit einem Gleichheitszeichen zu schreiben ist; zwei aufeinanderfolgende Gleichheitszeichen bewirken einen Test auf Gleichheit und liefern einen logischen Wert als Ergebnis.

    Ich kann der Versuchung nicht widerstehen, den Beitrag #107 ein zweites Mal zu beantworten, allerdings mit einer Tricklösung, von der ich weiß, dass sie nicht völlig naheliegend ist. Sie illustriert aber die Möglichkeiten von Python.


    ES ist möglich, zu schreiben:


    Python
        random.choice([motor_right, motor_left]) ()

    Das muss jetzt aber sorgfältig erklärt werden:

    • Das Aufrufargument von random.choice ist jetzt eine Liste, die die Namen von zwei im Programm definierten Funktionen enthält. Beachte, dass den Funktionsnamen hier keine Klammern folgen; die Funktionen werden also nicht aufgerufen. Das ist wichtig!
    • Wenn der Aufruf von random.choice ausgeführt wird, erhalten wir als Ergebnis eine der beiden in der Liste genannten Funktionen - wir wissen nur nicht, welche.
    • random.choice([motor_right, motor_left]) ist jetzt also ersetzt zu denken durch eine der Funktionen motor_right, motor_left. Jetzt kommt der Trick: Das nachgestellte Klammernpaar () bewirkt den Aufruf der Funktion, die durch die Zufallsfunktion ausgewählt wurde. Das ist aber wirklich ein Trick, und zwar einer für Personen, die in Python schon recht gut drin sind.
    • Immerhin ersparen wir mit diesem Trick eine mit if und else ausprogrammierte Fallunterscheidung.

    Der Aufruf random.choice([1, 2]) liefert einen Wert, und zwar immer etweder eine 1 oder eine 2. In dem gezeigten Code wird dieser Wert aber nicht verwendet, sondern sogleich "weggeworfen". Die Verwendung eines Wertes kann so geschehen:

    Python
        zufallswert = random.choice([1, 2])
        if zufallswert == 1:
            pass #  hier statt pass eine Aktion
        else:
            pass  # hier statt  pass eine Aktion

    Auf die Hilfsvariable zufallswert kann verzichtet werden, wenn geschrieben wird:


    Python
        if random.choice([1, 2]) == 1:
            pass
        else:
            pass

    Ich schlage versuchsweise folgendes vor:

    Python
        if random.choice([1, 2]) == 1:
            motor_left()
        else:
            motor_right()


    Schlussfolgerung: Es genügt nicht, einen Zufallswert zu erzeugen. man muss ihn auch kreativ nutzen.


    Nebenbemerkung: pass ist ein Schlüsselwort, das eine Anweisung ersetzt. Man schreibt es immer hin, wenn man eine Anweisung hinschreiben muss, aber noch nicht weiß, welche. Sobald man sich dann überlegt hat, was man eigentlich will, ersetzt man das pass durch das, was man sich überlegt hat.



    Nach dem Aufruf time.sleep(0.5) ist dann natürlich motor_stop() zu schreiben, damit die Drehung wieder aufhört.

    Der Aufsatz Cluster HAT 2.5 kann auf den Raspberry aufgesteckt werden und ist in der Lage, bis zu 4 Raspberry Zero anzusteuern. Das Ding kostet 31 Euro. Wenn man an jeden dieser Zeros eine Kamera anschließen will, kommt man mit allem Drum und dran aber sicher auch auf 150 Euro. Die entscheidende Frage ist aber, ob man die aufgenommenen Bilder schnell genug von den 4 Raspberry Zeroes zum Hauptrechner bringen kann und wieviel Programmieraufwand das bedeutet. Scheint mir ein reizvolles Projekt zu sein, aber ich finde es schwierig, Aufwand und Erfolgsaussichten abzuschätzen. Nach der maßgeblichen Seite https://clusterhat.com/ (und dort Setup) scheint der Aufsatz inzwischen auch für einen Raspberry Pi 4 als Steuerrechner verwendbar zu sein.

    Quote

    Mit der Tastaturkombination ctrl + c funktioniert es. dann zeigt es auch die finally print befehle an.

    Vielen tausend Dank.

    Das ist sehr erfreulich. Wenn Du jetzt noch in der Stimmung bist, weiterzuarbeiten, sollest Du bitte ausprobieren, ob der Aufruf motor-anhalten() tatsächlich erforderlich ist oder ob es auch ohne geht.


    Nachbemerkung: Dass Du ein Ablaufprotokoll hergestellt und das Ergebnis mitgeteilt hast, hat mir sehr geholfen. Es wurde dadurch nämlich klar, dass die Finalisierung übergangen wurde, und das schafft man wirklcih nur mit dem kill-Signal.

    Wie wird den diese "ewige" Schleife verlassen? Ich vermute mal, mit einer TastenKombination, aber mit welcher?


    Steuerung und Q gleichzeitig gedrückt liefert, wenn ich nicht irre, ein kill-Signal, da ist dann sofort Schluss.

    Steuerung und C gleichzeitig gedrückt liefert einen KeyboardInterrupt, da kommt die Finalisierung mit finally dann zum Zug.