Python: Unterschied zwischen den Versionen

Aus m-wiki
Zur Navigation springen Zur Suche springen
Zeile 37: Zeile 37:
  if __name__ == '__main__':
  if __name__ == '__main__':
     printBoard()
     printBoard()
=== Interpreter und Umgebung festlegen ===
Wie bei der [https://de.wikipedia.org/wiki/Bash_(Shell) Bash] kann man in der ersten Zeile den Interpreter des Skripts festlegen. Dazu wird folgende Zeile verwendet:
#!/usr/bin/env python


== Kommentare ==
== Kommentare ==

Version vom 24. Juli 2022, 22:15 Uhr

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.

Editoren

Da Python textbasiert ist, kann es in beliebigen Editoren erstellt werden.

IDLE wird von Python mitgeliefert und liefert über einen Popup die Parameter einer eingegebenen Funktion. Möchte man Autocomplete für Funktionen verwenden, so geht dies mit CTRL+Space.

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.

Prüfen, ob das Programm direkt gestartet wurde

Mit der Codezeile if __name__ == '__main__': prüft man, ob das Programm direkt aufgerufen oder über ein anderes Programm eingebunden wurde. Somit wird eine definierte Funktion nur beim Hauptprogramm automatisch aufgerufen. Wird die Datei hingegen als Bibliothek importiert, unterbleibt der Aufruf der Funktion.

def printBoard():
    print() 
if __name__ == '__main__':
    printBoard()

Interpreter und Umgebung festlegen

Wie bei der Bash kann man in der ersten Zeile den Interpreter des Skripts festlegen. Dazu wird folgende Zeile verwendet:

#!/usr/bin/env python

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

print und format

  • Für die Ausgabe wird der print() Befehl verwendet: print("Der Wert beträgt: "x).
  • Soll der Zeilenumbruch unterbunden werden, so wird am Schluss der Anweisung das Endzeichen auf ' ' geändert, welches standardmässig im Hintergrund ein newline ist: print("Der Wert beträgt: "x, end='') Dies wird vor allem in Schlaufen verwendet, wo man den Zeilenumbruch nach mehreren Ausgaben am Schluss explizit mit einer Bedingung durchführt. Dies wurde in Python 3 eingeführt und war in Python 2 noch anders. Infos im Internet.
  • 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).

Wichtig: Mit w werden vorhandene Dateien überschrieben. Will man an bestehende Dateien anhängen, so muss man a verwenden.

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. Hier werden nun schon verwendete Module mit Beispielen aufgeführt. Bei der Anwendung externer Module muss man diese zuerst von der Quelle einbinden. Dies geschieht entweder über apt-get, wie beim unten aufgeführten Beispiel des Webservers Bottle oder über git wie beim Einsatz des OLED-Displays auf der Raspi-Seite.

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)

Sound

Mit Hilfe von pygame können Sounds abgespielt werden. Die Bibliothek stellt dazu mixer zur Verfügung.

import pygame.mixer
from pygame.mixer import Sound
from gpiozero import Button
from signal import Pause

pygame.mixer.init() # Mixer initialisieren, damit Sounds gespielt werden können

button_sounds = { # Verzeichnis mit Schlüssel : Wert Zuweisung
    Button(2): Sound("samples/drum_cymbal_open.wav"),
    Button(3): Sound("samples/loop_mika.wav"),
}

for button, sound in button_sounds.items(): # Durchs Verzeichnis durchgehen
button.when_pressed = sound.play            # Den Sound wiedergeben, welcher dem gedrückten Knopf entspricht.

pause()                                     # Warten auf das nächste Event. So braucht es keine Endlosschleife.

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)

Piezo-Summer (Buzzer)

Der Summer erlaubt es, einfache Tonausgaben vorzunehmen. Eine Beschreibung erfolgt direkt im Artikel vom Raspi. Im Gegensatz zur Bibliothek von RPi gibt es zwei Funktionen. Der Standard-Buzzer lässt sich nur an- und ausschalten. Will man wie bei RPi die Tonhöhe verändern, so muss man auf TonalBuzzer ausweichen.

from gpiozero import Buzzer
buzzer = Buzzer(27)
buzzer.beep()
buzzer.off()
from gpiozero import TonalBuzzer
from gpiozero.tones import Tone
b = TonalBuzzer(17)
b.play(Tone("A4"))  # Angabe des Tons mit Oktave
b.play(Tone(220.0)) # Angabe des Tons in Hz

LC-Displays (LCD)

Wie man Displays auf Basis des Hitachi HD44780 einsetzt, ist im Artikel Raspi mit LC-Display beschrieben. Je nach Display braucht es aber noch einen Spannungskonverter, wenn man Daten vom Display 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.

Webserver (flask)

flask ist ein dynamischer Webserver, der entsprechend auch etwas komplizierter ist und mehr Möglichkeiten als bottle bietet.

Installaton

Um flask zu installieren braucht es zuerst pip, das bei neuerern Distributionen aber schon installiert sein sollte:

sudo apt-get install python3-pip

Danach kann flask installiert werden:

sudo pip3 install flask

Je nachdem ist dieser auch schon vorhanden. In diesem Fall kann eine erste Webseite erstellt werden.

Einbindung in ein Programm

Beim Aufbau ist es wichtig, die Struktur der Webseite korrekt aufzubauen, damit flask die Webseiten korrekt darstellen kann. Hier ein minimales Beispiel. In der oben verlinkten Webseite wird der komplette Aufbau gezeigt. Dateien im Ordner static bleiben wie der Name sagt statisch, wärend der Inhalt im Ordner templates dynamisch ändern kann. Die style.css und index.html sind prinzipiell nicht notwendig und alles könnte prinzipiell über die app.py aufgebaut werden. Aus Übersichtlichkeitsgründen sollte man aber CSS und HTML entsprechend in die dazugehörigen Dateien speichern und app.py nur für den Zugriff auf den Raspi einsetzen.

  Hauptverzeichnis des Projektes (z.B.: flask)
   ___________________|____________________
  |                   |                    |
Ordner: static    Ordner: templates    Datei: app.py
  |                   |
Datei: style.css  Datei: index.html

Inhalt von app.py:

# Notwendige Bibliotheken importieren
from gpiozero import LED
from flask import Flask, escape, request, render_template

# Instanz von flask generieren
app = Flask(__name__)

# LED zum Testen vorbereiten
led = LED(17) # LED definieren, damit man sehen kann, ob die Eingaben vom Webserver auch verarbeitet werden
led.off()     # Zur Ansteuerung der LEDs siehe den Abschnitt zur Hardware
led_state = 'LED ist ausgeschaltet'

# Webseite vorbereiten
@app.route('/')        # Die Webseite befindet sich im root-Verzeichnis des Webservers
def main():            # "Normale" Homepage dem Benutzer beim ersten Zugriff präsentieren
  global led_state     # den global definierten LED-Status in die Funktion einbinden
  return render_template('index.html', led_state = led_state) # Status der LED an die Webseite zurückgeben

@app.route('/<action>')# Die Webseite befindet sich im root-Verzeichnis des Webservers
def control(action):   # Auf dynamische Aktionen auf der Webseite reagieren
  global led_state     # den global definierten LED-Status in die Funktion einbinden
  if action == 'on':   # Testen, ob die Variable "action" mit dem Wert "on" vom Browser gesendet wurde (http://a.b.c.d/on)
    led.on()           # LED einschalten
    led_state = 'LED ist eingeschaltet'
  if action == 'off':  # Testen, ob die Variable "action" mit dem Wert "off" vom Browser gesendet wurde (http://a.b.c.d/off)
    led.off()          # LED ausschalten
    led_state = 'LED ist ausgeschaltet'
  return render_template('index.html', led_state = led_state) # Status der LED an die Webseite zurückgeben

if __name__ == '__main__':
  app.run(host = '0.0.0.0', port = 80, debug = True) # Auf alle Zugriffe auf Port 80 hören und alles rapportieren

Inhalt von index.html:

<!DOCTYPE html>
  <head>
    <title>Raspi Web-Server</title>
    <link rel = "stylesheet" type = "text/css" href = "{{ url_for('static', filename = 'style.css') }}" /> <!-- CSS-Datei aus dem static-Verzeichnis einbinden -->
    <meta name = "viewport" content = "width = device-width", initial-scale = "1" /> <!-- Für mobile Browser einrichten -->
  </head>
  
  <body>
    <h1>Raspi Web-Server</h2>
    <p>LED-Zustand: {{led_state}}</p> <!-- Den Wert der Variable einbinden -->
    <a href = "/on"><button>ON</button></a>
    <a href = "/off"><button class = "off">OFF</button></a>
  </body>
</html>

Inhalt von style.css:

a { text-decoration: none; }

body {
 display: inline-block;
 margin: 0px auto;
 text-align: center;
}

button {
  display: inline-block;
  margin: 0px auto;
  padding: 15px 25px;
  font-size: 3rem;
  border-radius: 4px;
  color: #fff;
  background-color: #009933;
  border: none;
}

.off { <!-- Pseudoklasse für ausgeschalteten Button -->
  color: #fff;
  background-color: #604f43;
}

h1 { font-size: 2.5rem; }

p { font-size: 1.8rem; }

Nachdem die Dateien erstellt wurden, kann die app über python gestartet werden. Da auf die IO zugegriffen werden soll, braucht es sudo:

sudo python app.py

JSON API verarbeiten

Python besitzt einen Interpreter, welchen man mit requests einbinden kann. Dieser serealisiert das empfangene Objekt, so dass man auf die Endpunkte mit einem einfachen get zugreifen kann. Der Vorteil von JSON gegenüber reinem XML besteht darin, dass der Aufbau einfacher ist, indem auf den Zugriff auf Knoten oder die Verwendung von Attributen verzichtet wird und nur mit Zuweisungen von Werten zu Elementen gearbeitet wird. Da jedes Element aber wieder Arrays und Subelemente enthalten kann, sind beliebige Verschachtelungen möglich. Um die Korrektheit der Daten zu analysieren, kann wie bei XML mit einem Schema gearbeitet werden, wobei es aber wie bei XML optional ist. Auch wenn JSON-Objekte gültige Javascript-Objekte sind, sollte aus Sicherheitsgründen nicht einfach mit eval() gearbeitet werden.

Um von einer Webseite die Antwort als JSON-Objekt zu erhalten, muss man die Programmierschnittstelle der Webseite ansprechen. Diese liefert dann je nach Berechtigung die gewünschten Daten. Damit nicht bei jeder Anfrage der Benutzer mit Passwort mitgeliefert werden muss, wird einmalig ein Token gelöst, welches dann eine bestimmte zeitliche Berechtigung hat. Je nach Anwendung wird das Token zeitlich unbegrenzt vergeben und muss entsprechend als Geheinmis gehütet werden, da jeder, der Zugriff aufs Token hat, mit der Identität des Token-Inhabers auf den Server zugreift. Bei anderen Anwendungen meldet man sich an und erhält ein Token mit begrenzter Laufzeit, welches nach einer bestimmten Fixzeit verfällt oder nachdem man sich eine bestimmte Zeit nicht mehr beim Server gemeldet hat.

Beim nachfolgenden Beispiel mit der openwheatermap muss man sich anmelden und erhält ein unbegrenzt gültiges Token, das als Geheimnis gehütet werden muss. Dieses Token besteht normalerweise aus einer Zahlen- und Buchstenfolge aus dem englischen Alphabet, damit diese einfach verarbeitet werden können. Eine API ist benutzerspezifisch und entsprechend muss man deren Syntax kennen, um Daten zu erhalten. Ein Beschrieb der API von openwheatermap findet sich auf der Seite https://openweathermap.org/api. Dort steht auch, wie man die Längen und Breitengrade einer Stadt bekommt: https://openweathermap.org/api/geocoding-api

import requests
wheater_data = requests.get('https://api.openweathermap.org/data/2.5/weather?lat=35&lon=139&appid=b00853...) # Im Gegensatz zu früher muss man Längen- und Breitengrad der gewünschten Stadt angeben.
# wheater_data2 = requests.get('http://api.openweathermap.org/geo/1.0/direct?q={city name},{state code},{country code}&limit={limit}&appid={API key} -> Veralteter Aufruf
temp_max = wheater_data.json().get('main').get('temp_max')
print('Maximale Temperatur: ', temp_max)

Emails versenden

Mit den Bibliotheken smtplib und email.mime.text können Emails (un)verschlüsselt versendet werden. Da die verwendete Bibliothek email heisst, darf das Skript entsprechend nicht email.py heissen. Damit der Zugriff auf gmail möglich ist, muss dort die Einstellung unsichere Apps aktiviert werden. Bei den meisten Internetprovidern ist ein Versand möglich, wenn man bei diesem angemeldet ist und sich entsprechend darüber verbindet.

import smtplib
from email.mime.text import MIMEText

from_email_adr = 'markus.grob@gmail.com' # Eigene Email-Adresse
from_email_pw = 'abc123'                 # Eigenes Email-Passwort
to_email_adr = 'snoopy@ilnet.ch'         # Empfänger-Emailadresse. Weitere Mailadressen mit Komma separiert anhängbar.

body = 'Das ist die Mail, die der Empfänger erhält.\nTest' # Emailtext -> Mit \n sollte ein Zeilenumbruch stattfinden. Wird evtl. nur ausgeführt, wenn man " anstatt ' für den String verwendet.
msg = MIMEText(body)                     # Erstellt ein MIME-konformes Email-Objekt, in welches der Body eingefügt wird.
                                         # Man hätte das Objekt natürlich auch "leer" erstellen und den Body wie alle anderen Parameter nachträglich setzen können.
msg['From'] = from_email_adr             # From-Parameter abfüllen
msg['To'] = to_email_adr                 # To abfüllen. In gleicher Weise kann man auch cc und bcc verwenden.
msg['Subject'] = 'Das ist der Titel'     # Nun den Titel setzen.

server = smtplib.SMTP(smtp.gmail.com', 587) # Je nach Provider muss man im Internet schauen, wie der Server heisst und auf welchem Port er Emails erwartet
server.starttls                          # Nur notwendig, falls der Server TLS verwendet.
server.login(from_email_adr, from_email_pw) # Meist wird als Benutzername die Emailadresse verwendet. Lautet diese anders, muss man hier den zu verwendenden Benutzernamen eingeben.
server.sendmail(from_email_adr, to_email_adr, msg.as_string()) # Neben Absender und Empfänger wird das Email-Objekt als String übergeben.
server.quit()                            # Die Verbindung sauber beenden
print('Email versendet')                 # Rückmeldung, dass das Email versendet wurde -> Hier müsste man noch eine Fehlererkennung einbauen, wenn der Kontakt fehlschlägt oder sonst etwas den Versand verhindert.

Grafische Programmierung mit Node-RED

Mit der grafischen Umgebung kann ein Programm im Browser "zusammengeklickt" werden. Man verbindet die einzelnen Elemente zu einem Ablauf, welcher dann Eventgesteuert oder Zeit-getriggert abläuft. Die Programmierung geschieht im Browser und auch die Bedienung wird darüber abgewickelt. Eine deutsche Einführung bietet das pdf: Node-RED dashboard Schnellstart- Anleitung- Erste Schritte, welches auch direkt im Anhang verfügbar ist: Datei:Node-red.pdf

Für viele Hardware-Komponenten bestehen Module (Nodes), welche über den Paketmanager von Node-RED eingebunden werden können.

Die Plattform steht für verschiedene Umgebungen wie Arduino, Android, Raspberri usw. zur Verfügung. Entsprechend hier ein Beispiel für die Ansteuerung von LEDs und die Auslesung eines Sensors.

Bevor man startet, muss geschaut werden, dass man eine aktuelle Version verwendet.

bash <(curl -sL https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered)

Anschliessend kann man eine Konfigurationsdatei mit den wichtigsten Einstellungen erstellen lassen. Die wichtigsten Einstellungen sind schon vorgewählt, so dass man sich einfach durchklicken kann.

node-red admin init

Neue Module hinzufügen

Je nach Element muss zuerst eine entsprechende Definition zur Ansteuerung/Auslesung hinterlegt werden. Dazu ist der "Node Package Manager" zuständig, der evtl. zuerst einmalig installiert werden muss.

sudo apt install npm

Anschliessend muss die c-Bibliothek zum ansprechen des Moduls heruntergalden werden, damit man es für Node-RED kompilieren kann. Hier ein Beispiel für den DHT22-Temperatursensor, welcher auf dem BCM2835 basiert. Auf der Webseite http://www.airspayce.com/mikem/bcm2835/ kann man die Bibliothek herunterladen. Vor dem Download halt schauen, dass man die aktuellste Version der Bibliothek verwendet und nicht einfach den Link kopieren. Welches Modul welche Bibliothek einbindet, muss man leider über eine Websuche herausfinden. nach dem Download wird das Modul entpackt und zur Verfügung gestellt.

mkdir nodered_module
cd nodered_module
wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.71.tar.gz
tar -xzvf bcm2835-1.71.tar.gz
cd bcm2835-1.71
./configure
make
sudo make check
sudo make install
cd ..

Nun kann das erstellte Modul in den NPM eingebunden werden.

sudo npm install node-dht-sensor
sudo npm install node-red-contrib-dht-sensor
sudo npm install node-red-dashboard

Anschliessend kann das Programm gestartet werden.

Node-RED verwenden

Das Programm muss als sudo gestartet werden. Dies, damit man auch Zugriff auf die I/O hat.

sudo node-red-start

Will man, dass das Modul beim Start automatisch läuft, fügt man es per systemd zum Start hinzu.

sudo systemctl enable nodered.service

Falls man die Ressourcen nicht mehr automatisch belegen will, wird es folgendermassen entfernt.

sudo systemctl disable nodered.service

Nach dem Start kann man mit der IP-Adresse des Geräts auf den Server zugreifen.

http://192.168.1.108:1880

Im Buch 20 easy Raspberry PiProjects wird im Kapitel "Project 17" Node-RED" vorgestellt und auch, wie bestimmte Elemente eingesetzt und verknüpft werden.

Soll das Programm im Hintergrund beim Start geladen werden, kann man es als Service einbinden:

sudo systemctl enable nodered.service

Soll das Programm aus Ressourcengründen gestoppt werden, so verwendet man:

node-red-stop

Modul nicht verfügbar

Ist ein Modul links nicht sichtbar, obwohl man es über die Kommandozeile installiert hat, wie etwas das Dashboard, so kann man es über die Einstellungen (Menü oben rechts) nachinstallieren. Man geht dort auf die Palette und unter "Installation" gibt man den Namen des Moduls ein, wenn es nicht schon unter "Installierte Module" aufgeführt ist. Sonst ist das Problem woanders zu suchen.

Es kann sein, dass nach der Grundinstallation nicht einmal das Dashboard verfügbar ist und entsprechend aus der Liste nachinstalliert werden muss.

Programmierung

Slider-Eigenschaften

Auch wenn die gesamte "Programmierung" grafisch geschieht, so muss man wissen, welche Nodes welche Funktionalität zur Verfügung stellen und in welcher Reihenfolge die Nodes verknüpft werden müssen, damit sie den gewünschten Outut liefern.

Die Eigenschaften einer Node werden mit einem Doppelklick geöffnet und müssen für die korrekte Funktion festgelegt werden. Damit Elemente beim Aufruf der grafischen Oberfläche (http://a.b.c.d:1880/ui) auch angezeigt werden, müssen sie entsprechend der Gruppe Dashboard zugeordnet werden.

Je nach Element, hat jedes seine spezifischen Eigenschaften, welche alle gesetzt werden müssen, bevor das Projekt rechts oben mit "Deploy"/Übernahme zur Verfügung gestellt werden kann. Fehlt eine Eigenschaft oder besitzt sie ungültige Werte, so wird das Objekt mit einem roten Dreieck gekennzeichnet.

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"