Python

Aus m-wiki
Zur Navigation springen Zur Suche springen

Einführung

Python ist eine Interpretersprache wie PHP oder SQL. Zur Zeit ist Python 3 im Einsatz und die Unterstützung von Version 2 ist beendet. Viele Programme für Python 2 laufen aber weiterhin ohne Anpassung unter Python 3. Die Sprache wird unter anderem häufig auf dem Raspberry Pi verwendet.

Python-Konsole

Wie andere Interpreter-Sprachen bietet auch Python eine Konsole, in der man Befehle direkt eingeben kann. Diese Konsole wird direkt mit python aufgerufen. Je nach Betriebsystem kann es sein, dass der Befehl noch auf Python 2 verweist und der Interpreter explizit mit python3 aufgerufen werden muss.

Danach wird direkt der Prompt für die Eingabe von Befehlen angezeigt. Da Python mit Einrückungen arbeitet, müssen diese auch in der Konsole angegeben werden. Um die Befehle abarbeiten zu lassen, wird nach der letzten Zeile 2x Enter gedrückt.

Wie bei der Bash kann man ältere Befehle mit den Pfeiltasten nochmals aufrufen, um ein wiederholtes Tippen zu vermeiden.

Python Programm ausführen

Wenn man den Namen des gespeicherten Programms angibt, wird dieses direkt interpretiert: python meinprogramm.py. Gibt es beim Ausführen Probleme wegen fehlender Zugriffsrechte, so muss man es als Root ausführen: sudo python meinprogramm.py
Beim Raspi kann man den Zugriff auf die GPIO auch ohne root verwenden.

Ausführung im Hintergrund

Dies ist mehr eine Eigenschaft vom Betriebsystem. Mit & wird die Ausführung in den Hintergrund verlagert:python meinprogramm.py &. Das System gibt dann die Prozess-ID zurück, mit der man das Programm wieder in den Vordergrund holen kann:fg 123 oder wieder mit dem Programmnamenfg python meinprogramm.py.

Kommandozeilen-Argumente auswerten

import sys
for (i,value) in enumeration(sys.argv):
    print("arg: %d%s" % (i, value))

Im argv-Objekt sind dann die Parameter drin. Im 0 ist der Programmname und danach die Parameter.

Kommentare

Als Kommentarzeichen gilt die Raute: # Alles nach dem Zeichen in einer Zeile wird ignoriert.

# Das ist ein Kommentar
print("Hallo") # Nun ein Kommentar

Mehrere Zeile werden mit """ eingeleitet und abgeschlossen:

"""
Das
sind
Kommentarzeilen
"""

Variablen

Variablen müssen nicht deklariert werden, sondern der Typ wird durch die Zuweisung bestimmt.

Variablen sollten immer mit einem Kleinbuchstaben beginnen. Will man längere Wörter, kann man diese mit Unterstrich trennen oder mit CamelCase:var var_var varVar.

Definition Typ
a = 123 Gazzahl (Integer)
a = 123.45 Fliesskommazahl (Float)
a = "Hallo" oder a = 'Hallo' oder a = '''Hallo''' oder a = """Hallo""" String. Die Varianten sind dazu da, um einzelne " oder ' im Text einfach zu übernehmen, wobei dies auch mit \" oder \' möglich wäre. Bei der Dreifach-Schreibweise sind auch Strings über mehrere Zeilen möglich.
a = True Bool (Gross-, Kleinschreibung wird beachtet)

Mit Variablen rechnen

Es werden die normalen Rechenoperationen unterstützt wie +-*/. Dazu % für Modulo (Teilung ohne Rest) und ** für die Potenzierung. Die Klammern werden berücksichtigt und für Bit-Operationen stehen noch weitere Möglichkeiten zur Verfügung.

Strings

  • Strings sind wie in Java fixe Arrays und können nicht verändert werden (generiert ein neues Objekt). Für den Zugriff auf einzelne Buchstaben kann man entsprechend den Array-Zugriff b = a[2] verwenden.
  • Zwei Strings kann man wie Zahl verknüpfen: c = a + b. Will man dieser aber mit einer Zahl, kommt eine Fehlermeldung. Hier muss man eine explizite Konvertierung vornehmen: c = a + str(15).
  • Will man einen String in eine Zahl konvertieren, so geht dies mit der entsprechenden Konvertierung: a = "15" b = int(a). Dies geht entsprechend auch mit float. Mit Angabe der Basis kann man auch in eine Binär- oder Hexzahl wandeln: a = int(1100,2) oder a = int(A9B3,16).
  • Die Länge eines Strings bestimmt man mit len(). Dies funktioniert auch mit einem Array.
  • Einen Substring kann man mit s = a.find("test") suchen, wobei dann der Index des ersten Zeichens geliefert wird oder -1, wenn nichts gefunden wurde.
  • Will man einen Substring nach der Suche kopieren, so gibt man den Bereich als Array an, wobei die erste Position den Startbuchstaben bestimmt und die zweite Zahl den Endbuchstaben, der aber nicht übernommen wird: s = a[2:5]. Lässt man die Zahl vor oder nach dem Doppelpunkt weg, so wird jeweils ab Anfang oder bis zum Ende genommen: s = a[:5] / s = a[2:].
  • Teilstring ersetzen wird mit a = "Hallo X" a.replace("X","Markus") durchgeführt, wobei Gross- und Kleinschreibung und auch Leerzeichen beachtet werden.
  • Mit a = "Hallo X" b = a.upper() wird der String in Grossbuchstaben und mit b = a.lower() in Kleinbuchstaben zurückgegeben, da Strings ja unveränderlich sind. Eine Neuzuweisung ist mit a = a.lower() möglich.

Arrays

Arrays werden in zwei Formen angeboten (Listen und Dictionaries).

Listen

Listen sind immer geordnet, doch können diese auch aus unterschiedlichen Datentypen bestehen. Der Zugriff auf Elemente geschieht über die Position. Eine Liste erkennt man an den eckigen Klammern. Bei den untenstehenden Beispielen werden die Listen immer nur in der Zeile beeinflusst. Das nächste Beispiel nimmt wieder die originale Liste.

a = [1, 3, 6, 2, 19]
b = [True, 45.3, "Hans"]
  • Die Nummerierung beginnt bei 0.
  • Für den Zugriff wird die Position angegeben: print(a[2]) -> 6
  • Das Veränderung eines Elements geschieht entsprechend auch über den Index: a[2] = 15 -> [1, 3, 15, 2, 19]
  • Mit a.append(9) wird das Element hinten an die Liste hinzugefügt: [1, 3, 6, 2, 19, 9]
  • Mit a.insert(3, 5) wird das Element an diese Position hineingeschoben: [1, 3, 6, 5, 2, 19]
  • Will man eine Liste an eine andere anhängen, so geht das mit extend: a.extend(b) -> [1, 3, 6, 2, 19, True, 45.3, "Hans"]
  • Mit pop() wird das letzte Element aus der Liste entfernt. Wenn man den Wert verarbeiten möchte, so kann man ihn an eine Variable übergeben, sonst wird er verworfen: c = a.pop() -> c = 19
  • Gibt man bei pop den Index an, so wird das Element an dieser Stelle entfernt: a.pop(0) -> [3, 6, 2, 19]
  • Will man bei einem Befehl auf einen nicht existierenden Eintrag zugreifen, so kommt ein "Index out of range"-Fehler. Entsprechend sollte man diesen immer mit einem try-catch-Block abfangen.
  • Mit split() kann man einen String in eine Liste umwandeln. Ohne Parameter wird der Space als Trennzeichen genommen und sonst das übergebene Zeichen: c = "AB CD--EF--GH".split("--") -> ["AB CD","EF","GH"]
  • Mit a.sort() -> [1, 2, 3, 6, 19] kann man eine Liste alphabetisch oder numerisch sortieren lassen. Da hier die Liste direkt angepasst wird, kann man (falls man beide Listen braucht), das Original mit dem copy-Befehl in eine andere Variable "retten". Da die Funktion ausgelagert ist, muss man zuerst die Bibliothek importieren: import copy b = copy.copy(a).
  • Möchte man einen Bereich aus der Liste weiter verarbeiten, so kann man dies mit dem :-Operator: b = a[2:4] -> [6, 2]. Auch hier ist die zweite Stelle nicht inklusiv. Lässt man einen Bereich weg, so gilt entsprechend vom Anfang oder bis zum Ende. b = a[2:] -> [6, 2, 19]. Es gibt auch die Möglichkeit, vom Ende der Liste her zu arbeiten, wenn man negative Zahlen einsetzt: b = a[-2:] -> [2, 19]. Entsprechend wird hier von der zweitletzten Position bis zum Ende gearbeitet.
  • Will man eine Funktion auf eine Liste anwenden, so kann man dies mit for und einer [] ganz kompakt: [x.upper() for x in a]. Hier wird die Anweisung zwar vor der Zuweisung ausgeführt (x wird ja erst nach dem for zugewiesen), doch der Code im Hintergrund referenziert korrekt, so dass man die Anweisung mit dem upper nicht erst in einer zusätzlichen Zeile machen muss.

Dictionarys

  • Ein Dictionary ist eigentlich eine Liste mit einem fix vergebenen Schlüssel. Der Vorteil ist vor allem die schnelle Suchgeschwindigkeit, wenn man den Schlüssel weiss. Im Gegensatz zu einer Liste wird ein Dictionary mit {} umschlossen. Der Schlüssel ist die erste Stelle und der Wert die Zweite. Ein Name für die Personalnummer würde also folgendermassen erstellt: a = {123:"Anton", 456:"Fritz", 987:"Susi"}
  • Ein Dictionary kann beliebige Werte für den Schlüssel annehmen. Auch Schachtelungen mit Verweisen auf Unterdirectories sind möglich. Hierbei wird an Stelle des Wertes einfach die Variable des einzusetzenden Directories gesetzt. b = {111:a}
  • Da der Key den Zugriff regelt ist die Reihenfolge nicht gleich dem Anlegen. Man kann daher nicht einfach mit b = a[2] auf Susi zugreifen, sondern muss den Key angeben:b = a[987].
  • Beim Zugriff auf einen nicht vorhandenen Key kommt es zu einem Fehler, so dass auch hier try catch verwendet werden sollte.
  • Ein neuer Eintrag wird mit a[333] = "Hans" hinzugefügt. Wie man sieht, werden hier wieder die eckigen Klammern verwendet, da das Directory ja schon angelegt ist. Vorsicht: Ist der Schlüssel schon vorhanden, so wird er überschrieben. Entsprehend sollte man vor dem einfügen prüfen, ob der Eintrag auch nicht existiert und entsprechend nachfragen, ob überschrieben werden soll.
  • Wie bei der Liste wird ein Eintrag mit pop entfernt. Da es aber keine eigentliche Reihenfolge gibt, muss immer der Key angegeben werden: a.pop(987)
  • Wie bei der Liste kann man auch hier mit for durch das Dictionary iterieren. Will man dabei den Schlüssel und den Wert verarbeiten, so vergibt man einfach für beide eine Hilfsvariable for key, value in a.items(): print(key+value)

Vergleiche

Es stehen die üblichen Vergleiche zur Verfügung wie <,>,=>,<=,== oder != / <> wobei != und <> äquivalent sind. Dazu kann man auch mit and und or verknüpfen, wobei die Schlüsselwörter klein geschrieben werden.

Beim vergleichen von Strings wird lexikalisch vorgegangen (a ist kleiner als b), doch Grossbuchstaben sind kleiner als Kleinbuchstaben (a > A -> True).

Verzweigungen

Im Gegensatz zu anderen Sprachen bietet Python der Einfachheit halber nur das if elif else ohne switch usw. Da Python seine Konnstrukte mit Einrückungen bildet, muss dies berücksichtigt werden, damit die Befehle korrekt interpretiert werden. Entsprechend sollte immer mit Leerzeichen und nie mit Tab eingerückt werden.

x = 100
if x > 99:
   print("Die Zahl ist mindestens dreistellig.")
elif x > 9
   print("Die Zahl ist mindestens zweistellig.")
elif x > -1
   print("Die Zahl ist mindestens einstellig.")
else:
   print("Die Zahl ist negativ.")
print("Diese Ausgabe kommt immer")

elif else können entsprechend weggelassen werden.

Schleifen

Python liefert hier zwei Typen für definierte und undefinierte Anzahl Durchgänge.

for

for i in range (1,11):
    print(i)

Die Laufvariable kann auch rückwärts angegeben werden (11,1). Die zweite Variable ist wie bei Arrays nicht inklusiv. Entsprechend läuft diese Schleife nur bis 10.

Die Schleife kann auch durch ein Array iterieren.

a = [1, "Hallo", 17.5, True]
for x in a:
    print(x)

Hier wird jeweils ein Element in die Hilfsvariable x kopiert und dieses dann ausgewertet, bis das Array/Liste durch ist.

Indexierte Arrays heissen in Python Dictionaries. Auch bei diesen kann for angewendet werden.

telefone = {"Hans":"+1234", "Fritz":"+4321"}
for name, nummer in telefone.items():
    print("Name: " name + " Nummer: " + num)

while

Hier ist die Abbruch-Bedingung nicht bekannt oder es gibt keine. Entsprechend wird die Schleife so lange durchlaufen, bis die Bedingung nicht mehr zutrifft. Etwa wenn man auf eine Tastatur-Eingabe wartet.

eingabe = ""
while eingabe != "x":
    eingabe = input("Bitte Befehl eingeben: ")
    print(eingabe)
print("Schlaufe verlassen nach Eingabe von x.")

Eine Schleife kann auch ohne Abbruchbedingung formuliert werden und mit einem break verlassen werden. So kann man mehrere Abbruchbedingungen schaffen:

eingabe = ""
while True:
    eingabe = input("Bitte Befehl eingeben: ")
    print(eingabe)
    if eingabe == 0:
        break
    if eingabe == "a":
        break
print("Schlaufe verlassen nach Eingabe von: "+eingabe)

Natürlich kann man auch eine Abbruchbedingung und break kombinieren, wenn dies sinnvoll erscheint.

Ausgabe

  • Für die Ausgabe wird der print() Befehl verwendet: print("Der Wert beträgt: "x).
  • Will man die Ausgabe formatieren, so wird format() eingesetzt. Für Werte mit 2 Stellen nach dem Komma: x = 1.2345 "x={:.2f}".format(x)
  • Entsprechend kann man auch die Zeit als String ausgeben: from datetime import datetime d = datetime.now() "{:%d.%m.%Y %H:%M:%S}".format(d)

Eingabe

Für die Eingabe von Tastaturevents kann Input genommen werden: eingabe = Input("Bitte eine Zahl eingeben: ")

Funktionen

Funktionsblöcke werden mit def funktionsname(parameter) erstellt. Parameter sind optional und können mit Standardwerten belegt werden. Mit return werden Werte zurückgegeben. Die Konvention der Benennung folgt der von Variablen und sollte entsprechend mit einem Kleinbuchstaben beginnen. Funktionsblöcke werden entsprechend eingerückt.

def addiere(eins = 5, zwei = 7):
    drei = eins + zwei;
    print(drei)
    return drei
print(addiere(9))

Es ist möglich, mehrere Werte zurückzugeben. Hierfür muss man nur beim return die entsprechenden Variablen kommagetrennt anhängen und entsprechend die geiche Anzahl Variablen beim Aufruf zur Verfügung stellen, so dass diese korrekt abgefüllt werden.

def addiere(eins = 5, zwei = 7):
    drei = eins + zwei;
    print(drei)
    return eins, zwei, drei
a,b,c = addiere(9)

Wenn man objektorientiert arbeitet, kann man natürlich auch ein Objekt mit den entsprechenden Werten zurückgeben.

Umgang mit Dateien

Zugriff auf Dateien

Da beim Zugriff Fehler passieren können, muss dies immer in einer Fehlerbehandlung abgefangen werden, so dass am Schluss der Close sauber ausgeführt wird. Natürlich muss man in einem Programm zuerst prüfen, ob die Datei existiert und ob man Test anhängen oder überschreiben will usw.

Der Pfad der Datei kann relativ zum eigenen Verzeichnis oder absolut mit dem Wurzelverzeichnis am Anfang / angegeben werden. Da Python platformunabhängig ist, sorgt Python dafür, dass Pfade korrekt mit dem Betriebsystem aufgelöst werden.

Für das Öffnen stehen verschiedene Flags zur Verfügung: r = read, w = write, a = append, b = binary, t = text (Standard), + = read and write. Man kann diese Flags auch mit + verknüpfen, wenn man etwa im binären Modus lesen will (r+b).

Datei schreiben / erstellen

Es braucht immer ein Handle für die Operationen. Entsprechend gilt immer folgendes Vorgehen:

  1. Fehlerhandling: try:
  2. Öffnen: f = open('test.txt','w')
  3. Schreiben: f.write('Test-Text')
  4. Schliessen: f.close()
  5. Fehler bearbeiten: except IOError: print("Fehler beim Schreiben der Datei")


Datei lesen

Es braucht immer ein Handle für die Operationen. Entsprechend gilt immer folgendes Vorgehen:

  1. Fehlerhandling: try:
  2. Öffnen: f = open('test.txt','r')
  3. Lesen: text = f.read()
  4. Schliessen: f.close()
  5. Fehler bearbeiten: except IOError: print("Fehler beim Lesen der Datei")

Es ist auch möglich, die Datei zeilenweise mit text = f.readline() einzulesen.

Datenstruktur in Datei verwalten

Python verwendet das "Pickling", um Daten zu speichern. Dabei wird einfach die gewählte Struktur abgespeichert und neu geladen. Auch hier sollte die Fehlerbehandlung integriert werden.

import pickle
mylist = ["abc", 1, 2, "Hans"]
f = open("mylist.pickle","w")
pickle.dump(mylist,f)
f.close()
f = open("mylist.pickle")
daten = pickle.load(f)
f.close()
print(daten)

Fehlerbehandlung

Sämtliche Befehle können mit try "versucht" werden. Bei einem Fehler wird nach except verwiesen. Im Erfolgsfall kann man auch einen else-Block ansteuern und zusätzlich am Schluss noch finally, das immer angesteuert wird.

list = [1, 2, 3]
try:
    list[5]
except Exception as e:
    print("Element nicht in der Liste")
    print(e)
else:
    print("Element innerhalb der Liste")
finally:
    print("Wird immer durchlaufen")

Das Exception-Objekt muss nicht verwendet werden, doch es liefert Informationen zum Fehler. Ist vor allem dann nützlich, wenn man Fehler "nach oben" weitrgibt und dann die wirkliche Ursache noch mitteilen möchte.

Objektorientierung

Klasse und Konstruktor

Wie bei Java wird zuerst eine Klasse erstellt und davon eine Instanz gebildet. Die Syntax ist aber etwas anders:

class Person:
    '''Kommentar zur Klasse Person'''
    def __init__(self, first_name, last_name, tel):
        self.first_name = first_name
        self.last_name = last_name
        self.tel = tel

Der Kommentar mit drei ' dient der Dokumentation der Klasse und kann später mit Person.__doc__ abgefragt werden, wenn man etwa eine externe Bibliothek einbindet.

Der Konstruktor heisst im Gegensatz zu Java immer init und hat vor- und nachher zwei Unterstriche zur Kennzeichnung. Als Parameter muss am Anfang jeder Methode (Funktionen heissen in objektorientierten Sprachen so) immer self stehen, das auf sich selbst referenziert. This im Gegensatz zu "this" bei Java. Die Klassenvariablen werden Member-Variablen genannt.

Eine Instanz dieser Klasse würde dann folgendermassen erstellt: p = Person("Hans","Muster","+41123")

Methoden

Methoden werden wie Funktionen erstellt, müssen aber wie erwähnt als ersten Parameter self haben:

def full_name(self):
    return self.first_name+" "+self.last_name

Eine Anwendung wäre dann print(p.full_name()) -> Hans Muster

Möchte man eine Methode innerhalb der Klasse aufrufen, so wird nicht der Klassenname verwendet, sondern ein self.methode()

Vererbung

Python beherrscht Mehrfach-Vererbung. Bei der Vererbung wird bei der Klassendefinition in Klammer die Elternklasse angegeben. Bei Mehrfachvererbung werden die Klassen einfach durch Kommas getrennt. Beim Vererben muss der Konstruktor der Elternklasse aufgerufen werden, bevor eigene Member-Variablen erstellt werden. Bei Python 3 geschieht dies mit super. unter Python 2 noch mit dem Klassennamen der Elternklasse Person. Bei mehreren Eltern müssen entsprechend alle aufgerufen werden.

class Arbeiter(Person):
    def __init__ (self, first_name, last_name, tel, gehalt):
        supper.__init__ (self, first_name, last_name, tel)
        self.gehalt = gehalt
    def gehalt_festlegen(self, gehalt)
        self.gehalt = gehalt
    def gehalt_erhoehen(self, gehaltserhoehung)
        self.gehalt += gehaltserhoehung

Externe Module / Bibliotheken einbinden

Dies geht ganz einfach mit dem Import Befehl. Hier kann man festlegen, ob man nur bestimmte Methoden oder alles verwenden will. Dies ist dann abhängig vom verfügbaren Speicherplatz. Wenn man nur eine bestimmte Methode braucht, dann lohnt es sich nicht, das ganze Modul zu holen, nur weil es schneller geschrieben ist.

import Modulname
import Modulname as X
from Modulname import *
from Modulname import Methode


  • Verwendet man nur den Modulnamen, so muss man nachher auf den Inhalt mit Modulname. zugreifen.
  • Beim Import mit dem * hat man alles zur Verfügung und kann ohne Modulname auf die Funktinen und Variablen zugreifen, doch dies braucht nicht nur Speicherplatz, sondern es kann auch zu Konflikten kommen, wenn man mehrere Module importiert und diese Methoden mit gleichem Namen besitzen.
  • Beim Import mit "as X" kann man auf den Inhalt des Moduls zugreifen, indem man X. verwendet. Dies kann bei langen Modulnamen sinnvoll sein, doch auch für Verwirrung sorgen, wenn man im Nachhinein zuerst wieder herausfinden muss, von welchem Modul diese Methode ist.

Alle existierenden Module findet man auf Python Dokumentation

Zufallselemente

from random import randint
random.randint(1,6)

Es wird eine int-Zahl zwischen den Bereichen generiert, wobei die Randzahlen eingeschlossen sind.

from random import choice
random.choice(["a", "b", "c"])

Aus der Liste wird ein zufälliges Element zurückgegeben.


Zeitfunktionen

Die Bibliothek time stellt Zeitfunktionen zur Verfügung. Sie wird unter anderem für Pausen verwendet. Mit der sleep()-Funktion kann man etwa die Pause in Sekunden angeben.

from time import sleep
sleep(1)

Zugriff auf Hardware

Die Bibliothek gpiozero bietet Zugriff auf die Hardware des Raspi.

LED

from gpiozero import LED
led = LED(17) # Generiert ein LED-Objekt, welches am Pin 17 angeschlossen ist
led.on()      # LED einschalten
led.off()     # LED ausschalten
from gpiozero import PWMLED # LED mit Pulsweitenmodulation dimmen
led = PWMLED(17)
led.value(0.5)   # LED auf den Wert 0.5 dimmen -> Kann etwa im Zusammenhang mit einem analogen Wert (Potentiometer) geschehen. Wird auch für RBG-LEDs verwendet.

Ein Beispiel mit Einsatz einer anderen Bibliothek sieht man im Beispiel PWM mit Schieberegler. Im Gegensatz zur anderen Bibliothek wird hier die PWM-Frequenz fix eingesetzt.

Taster (Button)

from gpiozero import Button, LED
button = Button(25) # Generiert ein Button-Objekt, welches am Pin 17 angeschlossen ist
button.when_pressed = led.on()      # Auf den Einschaltpuls reagieren
button.when_released = led.off()    # Auf den Ausschaltpuls reagieren

Analoge Werte einlesen

Im Gegensatz zum Arduino bietet der Raspi keine analoge Schntitstelle. Entsprechend muss man einen AD-Konverter-Baustein wie den MCP3008 einsetzen.

from gpiozero import MCP3008
messwert1 = MCP3008(0)
messwert2 = MCP3008(1)
print(messwert1", "messwert2)

LC-Displays

Wie man Displays auf Besis des Hitachi HD44780 einsetzt, ist im Artikel Rasi mit LC-Display beschrieben. Je nach Display braucht es aber noch einen Spannungskonverter, wenn man Daten lesen will, weil diese meist mit 5V gespiesen werden und die Pins des Raspi dies im Gegensatz zum Arduino nicht vertragen. Bei einer reinen Ansteuerung ist dies aber nicht notwendig, da dann der Raspi die Spannung setzt.

Systemsignale abfangen

Über signal hat man Zugriff auf Systemsignale des Betriebssystems.

from signal import pause
pause()

Diese Funktion ermöglicht es, das System ohne Dauerwhileschleife weiter laufen zu lassen. Wenn man etwa Taster abfragt, so kann man einfach diese Funktion am Schluss anfügen und das Programm beendet sich nicht einfach nach dem ersten Durchgang. Entsprechend muss es mit CTRL-c abgebrochen werden.

Webserver (bottle)

Es handelt sich um einen kleinen Webserver, der über eine Bibliothek eingebunden wird. Er kann mit entsprechender Konfiguration auch https verarbeiten. Es gibt auch ein entsprechendes deutsches Wiki.

Installaton

sudo apt-get install python3-bottle

Einbindung in ein Programm

from bottle import route, run, template # Einbindung der notwendigen bottle-Bibliotheken
from datetime import datetime

@route('/')                                  # Script an den root des webservers binden (http://localhost/)
def index(name = 'Zeit'):                    # Indes-Seite generieren mit dem Namen "Zeit"
    dt = datetime.now()                      # Aktuelles Zeit-Objekt holen
    time = "{:%d.%m.%Y %H:%M:%S}".format(dt) # Umwandeln in einen Datums- und Zeitstring.
    return template('<b>Datum:{{t}}</b>', t = time) # Über die template-Funktion das Datum ausgeben. 
                                                                     # Um auf die Variable im html-Code zuzugreifen muss der Python-Code in doppelte geschweifte Klammern gepackt werden.

run(host = '192.168.1.199', port = 80)       # Starten mit Angabe des Hosts und des Ports. Mit debug = true kann man entsprechende Fehlerausgaben protokollieren.

Im Artikel zum Raspi wird eine Einbindung der GPIO in den Webserver aufgezeigt.

Python-GUI

Für Python gibt es unterdessen mehrere Bibliotheken, um ein GUI einzubinden. Neben tkinter sind dies unter anderem WxPython, PyQt und PySide, PyGTK, Kivy sowie PyFLTK.

tkinter

tkinter ermöglicht die grafische Darstellung von Python-Programmen durch Anbindung an tcl tk.

GPIO-Pin (LED) schalten

Folgendes Beispiel schaltet einen GPIO-Pin auf Grund einer Checkbox. Quellcode auf github:

from tkinter import *     # gesamte Bibliothek einbinden.
import RPi.GPIO as GPIO   # Die RPi.GPIO eibinden und unter dem Namensraum GPIO zur Verfügung stellen.
import time               # Die time-Bibliothek einbinden.

GPIO.setmode(GPIO.BCM)    # BCM-Nummerierung verwenden.
GPIO.setup(18, GPIO.OUT)  # Pin 18 ansteuern.

class App:                # Die Klasse App eröffnen.

   def __init__(self, master):       # Konstruktor erstellen.
       frame = Frame(master)         # Ein Frame ist ein Fenster. Hier wird vom Hauptfenster geerbt.
       frame.pack()                  # Als Layoutmanager wird pack verwendet. Bei diesem werden die Elemente immer an den Rand der gewählten Richtung "gesetzt".
       self.check_var = BooleanVar() # Eine boolsche Variable definieren.
       check = Checkbutton(frame, text='Pin 18', command=self.update, variable=self.check_var, onvalue=True, offvalue=False) # Die Checkbox generieren.
       check.grid(row=1)             # Die Box im Layoutmanager grid unterbringen. Bei diesem werden die Elemente in Spalten und Reihen angeordnet.

   def update(self):      # Die update-Funktion bereitstellen
       GPIO.output(18, self.check_var.get()) # Den Pin auf Grund des Status der Checkbox setzen.

root = tk()                      # tk-Objekt erstellen
root.wm_title('On/Off Schalter') # Fenstertitel setzen
app = App(root)                  # Die Elemente aus der App-Klasse im Fenster platzieren
root.geometry("200x50+0+0")      # Die Fenstergrösse setzen
root.mainloop()                  # Endlosschlaufe

PWM mit Schieberegler steuern

Folgendes Beispiel steuert eine RGB-LED auf Grund von Schiebereglern. Quellcode auf github. Dort ist auch ein einfaches Beispiel mit einem Regler zu sehen. Standard-Befehle, die schon im obigen Beispiel kommentiert wurden, werden hier nicht mehr ausgeführt.

from Tkinter import *
import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)
GPIO.setup(23, GPIO.OUT)
GPIO.setup(24, GPIO.OUT)
 
pwmRed = GPIO.PWM(18, 500)
pwmRed.start(100)

pwmGreen = GPIO.PWM(23, 500)
pwmGreen.start(100)

pwmBlue = GPIO.PWM(24, 500)
pwmBlue.start(100)

class App:

   def __init__(self, master):
       frame = Frame(master)
       frame.pack()
       Label(frame, text='Red').grid(row=0, column=0)
       Label(frame, text='Green').grid(row=1, column=0)
       Label(frame, text='Blue').grid(row=2, column=0)
       scaleRed = Scale(frame, from_=0, to=100,
             orient=HORIZONTAL, command=self.updateRed)
       scaleRed.grid(row=0, column=1)
       scaleGreen = Scale(frame, from_=0, to=100,
             orient=HORIZONTAL, command=self.updateGreen)
       scaleGreen.grid(row=1, column=1)
       scaleBlue = Scale(frame, from_=0, to=100,
             orient=HORIZONTAL, command=self.updateBlue)
       scaleBlue.grid(row=2, column=1)
 
   def updateRed(self, duty):
       pwmRed.ChangeDutyCycle(float(duty))

   def updateGreen(self, duty):
       pwmGreen.ChangeDutyCycle(float(duty))

   def updateBlue(self, duty):
       pwmBlue.ChangeDutyCycle(float(duty))

root = Tk()
root.wm_title('RGB LED Control')
app = App(root)
root.geometry("200x150+0+0")
root.mainloop()

Das Beispiel kann natürlich auch für Gleichstrommotoren oder LED-Felder verwendet werden.

Werte aus Messung darstellen

Hier wird mit einem Ultraschallsensor die Distanz gemessen. Danach wird diese in einem Fenster dargestellt. Der Code für die Einlesung ist identisch.

from Tkinter import *
import RPi.GPIO as GPIO
import time

trigger_pin = 18
echo_pin = 23

GPIO.setmode(GPIO.BCM)
GPIO.setup(trigger_pin, GPIO.OUT)
GPIO.setup(echo_pin, GPIO.IN)

def send_trigger_pulse():
   GPIO.output(trigger_pin, True)
   time.sleep(0.0001)
   GPIO.output(trigger_pin, False)

def wait_for_echo(value, timeout):
   count = timeout
   while GPIO.input(echo_pin) != value and count > 0:
       count = count - 1

def get_distance():
   send_trigger_pulse()
   wait_for_echo(True, 10000)
   start = time.time()
   wait_for_echo(False, 10000)
   finish = time.time()
   pulse_len = finish - start
   distance_cm = pulse_len / 0.000058
   distance_in = distance_cm / 2.5
   return (distance_cm, distance_in)

class App:

   def __init__(self, master):
       self.master = master
       frame = Frame(master)
       frame.pack()
       label = Label(frame, text='Distance (inches)', font=("Helvetica", 32))
       label.grid(row=0)
       self.reading_label = Label(frame, text='12.34', font=("Helvetica", 110))
       self.reading_label.grid(row=1)
       self.update_reading()

   def update_reading(self):
       cm, inch = get_distance()
       reading_str = "{:.2f}".format(inch)
       self.reading_label.configure(text=reading_str)
       self.master.after(500, self.update_reading)

root = Tk()
root.wm_title('Range Finder')
app = App(root)
root.geometry("400x300+0+0")
root.mainloop()

Interrupts

Damit man bei Abfragen nicht immer eine Schleife bemühen muss und je nachdem wichtige Ereignisse verpasst, kann man sich auch einfach benachrichtigen lassen. Dies geschieht durch Einbinden der Interrupt-Behandlung.

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)

def my_callback(channel):
   print('Taster wurde gedrückt!')

GPIO.setup(18, GPIO.IN, pull_up_down = GPIO.PUD_UP) # Pin 18 initialisieren
GPIO.add_event_detect(18, GPIO.FALLING, callback = my_callback, bouncetime = 100) # Bei fallender Flanke wird die Routine aufgerufen. Bei einem internen Pull-Up-Widerstand ist dies beim Drücken der Fall. Bei einem Pull-Down wäre es Rising. Möchte man erst aufs loslassen der Taste reagieren, müsste man hier RISING setzen. Der Parameter bouncetime ist optional und verhindert die Weitergabe von zusätzlichen Interrupts, um etwa ein Tastenprellen zu unterbinden. Der Wert wird in Millisekunden angegeben.

i = 0
while True:
   i = i + 1
   print(i)
   time.sleep(1) # Immer eine Sekunde Pause machen. Ohne Interrupt würden Tastendrücke in dieser Zeit "ignoriert"