Instructions

User Manual:

Open the PDF directly: View PDF PDF.
Page Count: 124

DownloadInstructions
Open PDF In BrowserView PDF
Christina Rost
Embedded Systems - Technische Informatik. 3. Semester.
bei Prof. Dr.-Ing. Jürgen Schüle, HS AA
Hochschule für Gestaltung Schwäbisch Gmünd Internet der Dinge - Gestaltung vernetzter Systeme
in Kooperation mit der Hochschule für Technik und Wirtschaft Aalen

RainbowWarrior
Eine Einführung in die
Microcontrollerprogrammierung mit MicroPython
und dem ESP32 Development Board

1

Christina Rost
Embedded Systems - Technische Informatik. 3. Semester.
bei Prof. Dr.-Ing. Jürgen Schüle, HS AA
Hochschule für Gestaltung Schwäbisch Gmünd Internet der Dinge - Gestaltung vernetzter Systeme
in Kooperation mit der Hochschule für Technik und Wirtschaft Aalen

Inhaltsverzeichnis
1.

- Vorbereitungen........................................................................................................3
1.1 - Materialien............................................................................................................3
1.2 - Das ESP32 Development Board...........................................................................5
1.3 - Eine kurze Einführung ins Terminal.....................................................................7
1.3.1 - Terminal öffnen.............................................................................................7
1.3.2 - Aktuelles Arbeitsverzeichnis.........................................................................8
1.3.3 - Ordnerinhalt auflisten....................................................................................8
1.3.4 - Verzeichnis wechseln....................................................................................8
1.3.5 - Dateien kopieren............................................................................................9
1.4 - Betriebssystem und Framework installieren.....................................................11
1.4.1 - Abhängigkeiten............................................................................................11
1.4.2 - freeRTOS......................................................................................................11
1.4.3 – Nützliches Wissen......................................................................................16
1.4.4 – ESP32 mit Computer und LED Strip verbinden.........................................19
2. – LED Animationen mit Micropython.....................................................................21
2.1 - Module................................................................................................................21
2.1.1 - Das machine-Modul.....................................................................................22
2.1.2 - Das neopixel-Modul.....................................................................................22
2.2 - LEDs leuchten lassen.........................................................................................24
2.2.1 - Farben..........................................................................................................25
2.3 Variablen undDatentypen.....................................................................................27
2.3.1 - Variablen in Python......................................................................................27
2.3.2 - Datentypen in Python..................................................................................27
2.4 – Einfache Animation...........................................................................................30
2.4.1 - Blinken..........................................................................................................30
2.5 - Schleifen.............................................................................................................30
2.5.1. - For-Schleife.................................................................................................31
2.5.2 - While-Schleife..............................................................................................33
2.6 - Operatoren und Ausdrücke................................................................................34
2.6.1 - Arithmetische Operationen.........................................................................34
2.6.2 - Vergleichsoperatoren..................................................................................35
2.6.3 - Logische Operatoren...................................................................................35
2.7 - Funktionen..........................................................................................................36
2.7.1 - Funktionen mit optionalen Parametern......................................................38
2.8 - Ein erstes kleines Skript.....................................................................................40
2.8.1 - Editor............................................................................................................40
2.8.2 - Skript auf dem ESP ausführen....................................................................44
2.8.3 - Aufgabe: Schleifen erstellen.......................................................................44
2.9 - Dateisystem auf dem ESP..................................................................................45
2.10 - Animationen Teil 2: running dots.....................................................................47

2

Christina Rost
Embedded Systems - Technische Informatik. 3. Semester.
bei Prof. Dr.-Ing. Jürgen Schüle, HS AA
Hochschule für Gestaltung Schwäbisch Gmünd Internet der Dinge - Gestaltung vernetzter Systeme
in Kooperation mit der Hochschule für Technik und Wirtschaft Aalen

2.11 - Kommentare in (Micro)Python........................................................................50
2.11.1 - Zeilenkommentare....................................................................................50
2.11.2 - Blockkommentare / Docstrings................................................................51
2.12 - Animationen Teil 3: white level........................................................................52
3 - Steuerung der Animationen mit dem Touch Sensor...............................................54
3.1 - Der Touch Sensor...............................................................................................54
3.1.1 - Animationen mit dem Touch Sensor starten.............................................56
3.2 - Bedingte Anweisungen / Verzweigungen.........................................................57
3.3 - Iterator................................................................................................................61
3.4 - Lokale und globale Variablen in Python............................................................62
3.5 - Multithreading....................................................................................................64
3.5.1 - Implementierung von _thread.................................................................66
3.6 - Helligkeit der LEDs steuern................................................................................68
3.6.1 - Der Photowiderstand...................................................................................69
4 – Steuerung der Animationen mit dem WebServer...................................................74
4.1 - WLAN auf dem ESP............................................................................................74
4.1.1 - Das network-Modul.....................................................................................74
4.1.2 - Das microWebSrv-Modul.............................................................................78
4.2 - HTML..................................................................................................................80
4.3 - Eingaben der Webseite verarbeiten...................................................................86
4.3.1 - Route-Handler des microWebSrv Moduls..................................................86
4.3.2 - Thread-Messages vom Server in main.py empfangen und verarbeiten....88
4.3.3 - Animationen mit einem String starten........................................................90
4.3.4 - Zwei ähnliche Routinen in einer Funktion zusammenfassen....................90
5 - RainbowWarrior als Wecker nutzen.........................................................................92
5.1 - Der Piezo-Speaker..............................................................................................92
5.1.1 - PWM - Pulsweitenmodulation (Pulse-Width-Modulation).........................92
5.1.2 - Piezo-Speaker mit dem ESP verbinden......................................................94
5.1.3 - Das RTTTL-Modul........................................................................................94
5.1.4 - Real Time Clock - das RTC Modul...............................................................95
5.1.5 - Zeitabhängige Funktionen - das utime-Modul............................................96
5.1.5 - Songs mit dem PWM abspielen..................................................................97
5.2 - Dropdown Menü für Songs in die Webseite einfügen....................................102
5.2.1 - Verarbeitung der Daten auf dem Webserver............................................103
5.2.2 - Verarbeitung der Daten in main................................................................105
5.3 - Systemzeit auf der Webseite anzeigen...........................................................106
5.3.1 - String Formatierung...................................................................................107
5.3.2 - Formatierung der Systemzeit....................................................................109
5.4 - Systemzeit und Datum über die Webseite einstellen.....................................113
5.4.2 - Das Timer-Modul.......................................................................................117
5.5 - Alarm stoppen..................................................................................................120

3

Christina Rost
Embedded Systems - Technische Informatik. 3. Semester.
bei Prof. Dr.-Ing. Jürgen Schüle, HS AA
Hochschule für Gestaltung Schwäbisch Gmünd Internet der Dinge - Gestaltung vernetzter Systeme
in Kooperation mit der Hochschule für Technik und Wirtschaft Aalen

4

Christina Rost
Embedded Systems - Technische Informatik. 3. Semester.
bei Prof. Dr.-Ing. Jürgen Schüle, HS AA
Hochschule für Gestaltung Schwäbisch Gmünd Internet der Dinge - Gestaltung vernetzter Systeme
in Kooperation mit der Hochschule für Technik und Wirtschaft Aalen

1.

- Vorbereitungen
1.1 - Materialien

Für einfache Pixel-Animationen:
•
•
•
•

ESP32 Development Board
LED Strip SK6812 RGBW, ca. 170 mm lang (oder nach Belieben)
Breadboard
Jumperwires

Für das gesamte Projekt zusätzlich:
Neben der gezeigten Variante gibt es sehr viele verschiedene Möglichkeiten, LED auf
Plexiglas in Szene zu setzen, siehe im Kapitel "Plexiglas“. Hierbei darf man auch
gerne kreativ seiin! Jedoch sollte bei abweichendem Design im Vorfeld überlegt
werden, wie groß das Objekt insgesamt, wie die Plexiglasplatte und wie lang der Strip
sein sollte. Mehr dazu im Kapitel "Prototyp bauen".
• Plexiglasplatte - 170 x 170 x 10 mm (oder nach Belieben)
• Holzsockel, passend zur Platte. Hier ca. 50 x 70 x 190 mm
• Metallknopf als Touch-Sensor (gerne auch Ersatzknöpfe von Hosen,
schöne Nägel o.ä.)
• Piezo Speaker (passiv)
• Photoresistor
• Widerstand, 1kΏ (braun – schwarz – rot – gold)
• Litzen in 3 Farben (Schwarz, rot, gelb oder andere)
Des weiteren werden folgende Werkzeuge benötigt:
•
•
•
•
•
•
•

Lötkolben, Lötzinn, Lötsauglitze
Holzsäge, Kreissäge oder die Möglichkeit, Holz zu verarbeiten
Bohrmaschine incl. Bohrer in 5mm, ggf. auch 35mm
Schleifpapier (am besten ein gröberes und ein recht feines)
Heißklebepistole
ggf. Öl, Lasur, oder Lack um das Holz zu versiegeln
Computer inklusive USB Kabel.

5

Christina Rost
Embedded Systems - Technische Informatik. 3. Semester.
bei Prof. Dr.-Ing. Jürgen Schüle, HS AA
Hochschule für Gestaltung Schwäbisch Gmünd Internet der Dinge - Gestaltung vernetzter Systeme
in Kooperation mit der Hochschule für Technik und Wirtschaft Aalen

Die Anleitung bezieht sich auf Rechner mit installiertem Linux Mint System. Soweit
es mir möglich ist, werde ich versuchen, auch die nötigsten Schritte für Windows und
MacOS bereitzustellen. Für Vollständigkeit kann ich leider keine Garantie geben,
jedoch hilft hier oft eine kurze Internetrecherche zur betreffenden Fehlermeldung.

6

Christina Rost
Embedded Systems - Technische Informatik. 3. Semester.
bei Prof. Dr.-Ing. Jürgen Schüle, HS AA
Hochschule für Gestaltung Schwäbisch Gmünd Internet der Dinge - Gestaltung vernetzter Systeme
in Kooperation mit der Hochschule für Technik und Wirtschaft Aalen

1.2 - Das ESP32 Development Board
Beim ESP32 Development Board handelt es sich um einen Mikrocontroller, auch
gerne Ein-Platinen-Computer oder Minicomputer genannt.
Neben dem 240 MHz DualCore Prozessor ist der ESP32 noch mit 520 KiB SRAM
ausgestattet (SRAM = Static Random Access Memory - also ein Arbeitsspeicher, der
Werte und Dateien zwischenspeichern kann solange er mit Strom versorgt wird - wie
bei einem Computer. Kib (Kibibyte, 1 KiB = 1024 Byte) und 4 MiB flash-Speicher (=
flash EEEPROM, ein Speicherbereich, der nicht verloren geht, sobald kein Strom da
ist)
Zum Vergleich: Heute haben die meisten PCs (teilweise auch viele Smartphones)
mindestens einen Dual-Core Prozessor mit ca. 2.5 GHz, das wäre also mindestens
100 Mal schneller als der ESP32.
Außerdem verfügen ebenso die meisten PCs über mindestens zwei, eher noch vier
GB an Arbeitsspeicher, sprich vier bis achtmal so viel.
Dies sollte allerdings nicht abschrecken, da bei 240 MHz immerhin durchschnittlich
circa 240,000,000 Rechenoperationen pro Sekunde durchgeführt werden. Und dies
ist eine beachtliche Menge - absolut ausreichend für einen Micro Controller, mit dem
man lediglich "kleine" Aufgaben im Vergleich zum PC erledigen möchte.
Neben der Rechenleistung und dem verfügbaren Speicher bringt der ESP noch 34
GPIO Pins (General Purpose Input/Output Pins) mit sich, das sind also 34
sogenannte Pins (kleine Metallstifte), an denen man über ein angeschlossenes
Jumperwire (ein Kabel mit passendem Anschluss für die Pins) zum Beispiel Werte
eines Feuchtigkeits- oder Temperatursensors auslesen, Werte an einen elektrische
Motor oder - wie in unserem Fall - an einen LED-Strip senden kann.
Außerdem sind bereits einige Sensoren integriert: Der Touch-Sensor beispielsweise,
der auf Berührung reagiert, oder der Hall-Sensor, der Schwankungen im
Erdmagnetfeld misst. Außerdem ist bereits ein Temperatursensor integriert.
Zusätzlich kann dieses Board nicht nur eine Funkverbindung per WLAN bereitstellen
oder sich per WLAN mit einem bestehenden (Heim-) Netzwerk verbinden, sondern
ebenfalls eine Verbindung per Bluetooth herstellen.
Auf dem ESP32 läuft ein Mini-Betriebssystem namens freeRTOS (free Real Time
Operating System) im Hintergrund. Dadurch hat man zum Beispiel die Möglichkeit
zeitgesteuerte Aufgaben zu erstellen.
Die Einsatzmöglichkeiten sind sehr vielfältig. So kann man mit dem ESP32
7

Christina Rost
Embedded Systems - Technische Informatik. 3. Semester.
bei Prof. Dr.-Ing. Jürgen Schüle, HS AA
Hochschule für Gestaltung Schwäbisch Gmünd Internet der Dinge - Gestaltung vernetzter Systeme
in Kooperation mit der Hochschule für Technik und Wirtschaft Aalen

zum Beispiel die Wetterdaten oder einfache Daten im Haus zur Luftfeuchtigkeit,
Luftdruck und Temperatur messen und protokollieren. Ebenso kann man damit
elektrische Motoren steuern - per WLAN oder Bluetooth.
Man kann die integrierten Touch-Sensoren - oder einfach Buttons, die angeschlossen
wurden - nutzen, um etwas ein- oder auszuschalten.
Oder man kann sich - wie hier geschehen - LED Strips kaufen und für diese
Animationen programmieren. Einen Webserver erstellen, der eine Webseite zur
Steuerung der Animationen bereitstellt. Zusätzlich kann man den integrierten TouchSensor verwenden, um diese Animationen zu starten. Man kann einen Piezo-Speaker
nutzen, um Töne, oder gleich ganze Melodien zu spielen und einen Photowiderstand,
um die Helligkeit der LEDs an die Umgebung anzupassen.
Programmieren kann man den ESP32 auf drei verschiedene Arten, bzw. drei
verschiedenen Frameworks programmieren:
Arduino: Arduino ist - nach dem Raspberry Pi - bestimmt der bekannteste EinPlatinen-Computer. Daher wird dies wohl auch die geläufigste Sprache bzw. das
geläufigste Framework sein. Arduino basiert auf C++/C. Man kann, nachdem man
einige Einstellungen in der Entwicklungsumgebung für Arduino vorgenommen hat einfach mit derselben Sprache und derselben Programmierumgebung (IDE =
Integrated Development Environment), die man für den Arduino nutzt, auch den
ESP32 programmieren.
MicroPython: MicroPython - eine abgespeckte Version von Python) zeichnet sich
dadurch aus, dass sie von Anfängern gerne als leicht lernbar eingestuft wird.
Allerdings ist hier die Einrichtung, die man im Vorfeld vornehmen muss, etwas
komplexer.
ESP-IDF: Das ESP-IoT-Development Framework basiert ebenso auf C++ und ist das
offizielle Framework, das vom Hersteller der ESP entwickelt wurde. Dies erfordert sowohl für die Einrichtung als auch für den Upload und die Ausführung des Codes
einige Grundkenntnisse.

8

1.3 - Eine kurze Einführung ins Terminal
Mit dem Terminal hat man die Möglichkeit, über Befehle in geschriebener Form den
kompletten PC steuern. Hier kann man Dateien anlegen, suchen und packen sowie
System-, Netzwerk- oder Hardware-Befehle ausführen oder Benutzer verwalten.
Es sieht zwar sehr simpel aus, ist aber umso mächtiger im Vergleich zu den
Optionen, die auf der grafischen Oberfläche zu Verfügung stehen. So können manche
Aufgaben oft schneller und sauberer erledigt werden. Manche Anwendungen stellen
auch separate Kommandozeilen-Dienstprogramme zur Verfügung, welche
Funktionen bieten, die über die grafische Nutzeroberfläche des Programms nicht zu
erreichen sind.

1.3.1 - Terminal öffnen
Unter Linux: Super - btw. Starttaste drücken und "Terminal" eingeben. Alternativ kann
mit STRG+ALT+T per Tastatur das Terminal geöffnet werden
Windows: Windows-Taste drücken, "cmd" eingeben, mit Enter bestätigen
MacOS: CMD Leertaste drücken um Spotlight zu öffnen, "Terminal" eingeben.
Die hier vorgestellten Befehle beziehen sich hauptsächlich auf Linux. Viele der
Befehle sind auch auf MacOS zu finden. Einige auch auf Windows - manche davon
aber leicht abgewandelt.
Eine kurze Auflistung der wichtigsten Befehle pro Betriebssystem befindet sich am
Ende des Dokuments!
Auf den ersten Blick sieht das
Terminal überhaupt nicht
spektakulär aus.
Jede Zeile beginnt mit dem
Nutzernamen, gefolgt von dem
Computernamen (getrennt durch ein
@) Anschließend wird der
Ordnername angezeigt, in dem man
sich aktuell befindet. Hier sieht man
nur ein unscheinbares „~“, welches
aussagt, dass man sich gerade im
Home-Verzeichnis befindet.
Abgeschlossen wird die Zeile mit
einem „$“, das zum einen
signalisiert, dass man gerade als User (nicht etwas als Admin oder Superuser) im

Terminal angemeldet ist, zum anderen auch zeigt, dass auf eine Nutzereingabe
gewartet wird und nicht etwa ein Befehl gerade ausgeführt wird.

1.3.2 - Aktuelles Arbeitsverzeichnis
pwd (=print working directory)
Falls je nicht sicher sein sollte, ob man sich im richtigen Ordner befindet, kann man
einfach pwd eingeben, was nichts weiter bedeutet als „gib aktuelles
Arbeitsverzeichnis aus“

1.3.3 - Ordnerinhalt auflisten
ls (= list)
Mit dem Befehl ls gibt man den Inhalt eines Ordners aus. Nur ls, ohne Optionen,
listet den Inhalt des aktuellen Arbeitsverzeichnisses, während man mit
$ ls /home/$USER/Bilder
unabhängig vom derzeitigen Arbeitsverzeichnis - zum Beispiel den Inhalt des Ordners
"Bilder" im home-Verzeichnis des aktuellen Benutzers (= $USER) ausgeben.

1.3.4 - Verzeichnis wechseln
cd (=change directory)
Um in ein Unterverzeichnis zu wechseln, gibst du einfach den Befehl cd ein, gefolgt
durch den Namen des Unterverzeichnisses. Leerzeichen zwischen diesen beiden
nicht vergessen!
$ cd Bilder
wechselt also in einen Unterordner namens "Bilder", der sich im aktuellen
Arbeitsverzeichnis befinden muss.
Tipp für Schreibfaule: Man kann auch einfach die ersten Buchstaben des
Verzeichnisses eingeben. Mit einem Klick auf die TAB taste erledigt die
Autovervollständigung den Rest der Schreibarbeit – es sei denn es gibt mehrere
Dateien oder Verzeichnisse mit dieser Zeichenfolge am Anfang, oder es befindet sich
ein Schreibfehler darin.

Möchte man in ein ganz anderes Verzeichnis wechseln, gibt man einfach cd ein,
gefolgt vom kompletten Verzeichnispfad ein. Dieses Mal muss allerdings ein / vor
den Pfad, um zu signalisieren, dass es sich nicht um ein Unterverzeichnis handelt
$ cd /tmp
wechselt nun in das temporäre Verzeichnis namens "tmp", dass sich im RootVerzeichnis (Unix-Äquivalent zu Windows' "C:\"-Laufwerk.
Um wieder ins Übergeordnete Verzeichnis zu wechseln, einfach cd ..
eingeben.
$ cd ..

1.3.5 - Dateien kopieren
cp (=copy)
Um eine Datei zu kopieren, benutzt man den Befehl cpC(= copy) Hierzu wird mit cd
in den Ordner gewechselt, in dem sich die Datei befindet.
Anschließend muss eingeben werden:
$ cp dateiname.html /home/$USER/htmlFiles
Dieser Befehl besteht aus drei Teilen, getrennt durch ein Leerzeichen:
der Befehl cp, gibt an, dass eine Datei kopiert werden soll.
die Datei, die kopiert werden soll (hier als Beispiel: "dateiname.html")
der komplette Pfad zum Zielordner (/home/$USER/htmlFiles).
Wer allerdings Ordner anstelle von Dateien kopieren möchte, muss noch die Option r , was für Rekursiv steht, hinter den Befehl cd gehängt werden. So wird der Ordner
inklusive allen darin enthaltenen Dateien und Ordnern kopiert.

Das Ganze kann dann so aussehen:
$ cp -r htmlFiles /home/chrissi
Achtung! Ein wichtiger Unterschied zwischen Terminal und der grafischen
Benutzeroberfläche: Das Terminal meckert nur, wenn etwas nicht stimmt!
Es ist also völlig normal, dass nach Eingabe des Befehls und Bestätigung mit der
Enter-Taste einfach wieder Name/Computername + Arbeitsverzeichnis angezeigt
werden! Das signalisiert, dass alles geklappt hat.

Zudem gibt es keine Sicherheitsabfrage und keinen Papierkorb! gelöscht ist
gelöscht. Verschoben ist verschoben. Solange du Lese- und Schreibrechte in den
betreffenden Ordnern und den Dateien hast, wird es keine Sicherheitswarnung geben.
Daher ist hier große Vorsicht geboten!
Dies waren erst einmal die wichtigsten Befehle, damit wir erst einmal im Terminal
zurecht finden. Weitere Befehle werden an gegebener Stelle folgen und nochmals
erklärt.

1.4 - Betriebssystem und Framework installieren
1.4.1 - Abhängigkeiten
Bevor es mit der Installation des Betriebssystems und des Micropython Frameworks
losgehen kann ist die Installation von einigen kleinen (Software-)Paketen notwendig,
um mit (Micro)Python arbeiten zu können und damit der PC in der Lage ist, eine
Verbindung zum ESP aufzubauen um z.B. Dateien zu übertragen.
Außerdem müssen wir das Framework herunterladen.
Unter Linux wird hierzu einfach das Terminal geöffnet und folgende Befehle
nacheinander eingeben. Nach jeder Zeile muss mit Enter bestätigt werden. Achtung:
der erste Befehl geht über zwei Zeilen!:
$ sudo apt-get install git wget make libncurses-dev flex bison
gperf python python-serial python-pip rsync
$ sudo pip install --upgrade pip
$ sudo pip install esptool –upgrade
Unter Mac OS sind folgende Befehle nötig (ohne Gewähr! Falls etwas fehlen sollte →
Google fragen):
$ sudo easy_install pip rsync
$ sudo pip install pyserial

1.4.2 - freeRTOS
FreeRTOS (free Realtime Operating System) ist nichts weiter als ein
Echtzeitbetriebssystem, zugeschnitten für Mikrocontroller. Es basiert auf einer
Mikrokernelarchitektur und wurde auf verschiedene Mikrocontroller portiert.
Nur. was ist ein Kernel?
Ein Kernel, oder auch Betriebssystemkern ist - wie der Name schon sagt, der Kern,
das Herzstück eines jeden Betriebssystems. Hier wird die Prozess- und
Datenorganisation festgelegt, außerdem hat er direkten Zugriff auf die Hardware.
Das bedeutet, der Kernel steuert, wann welcher Prozess den Prozessor belegen darf,
welchen Speicherbereich dieser benutzen darf, wie die Hardware mit den Prozessen
kommuniziert und so weiter.

Falls zusätzlich Lesebedarf besteht, hier einige zusätzliche Informationen:

https://de.wikipedia.org/wiki/FreeRTOS
https://www.searchdatacenter.de/definition/Kern
el

1.4.2.1 - USB Port ermitteln
Bevor mit der Einrichtung des Frameworks begonnen werden kann, ist es wichtig zu
wissen, auf welchem USB Port der ESP angesprochen wird. Jedes USB-Gerät wird
vom PC an einen speziellen Port geknüpft, mit dem es vom System angesprochen
wird.
Unter Linux, sehr wahrscheinlich auch unter MacOS, ist dies für den ESP32 meist
"ttyUSB0" oder "ttyUSB1".
Sobald der Controller eingesteckt ist, kannst du dies auch einfach prüfen:
$ ls /dev/ttyUSB*
ls bedeutet list, sprich es listet nun alle angeschlossenen Geräte (gespeichert im
Ordner /dev), die mit "ttyUSB" anfangen. Der * signalisiert, dass nach dieser
Zeichenfolge noch andere Zeichen folgen können.
Falls mit dem Befehl ls keine Ausgabe erfolgen sollte, kann man auch die
Kernelmeldungen auslesen. Hierfür nutzt man - leider wieder nur unter Linux und
MacOS - den Befehl
dmesg (=display messages)
Um die Arbeit des
Kernels zu
demonstrieren, kann man
den Befehl einmal
ausführen bevor die
Hardware eingesteckt
wurde,

und einmal, nachdem der Controller
eingesteckt wurde. Der markierte
Bereich zeigt alle Meldungen bezüglich
des zuletzt eingesteckten USB-Geräts.
Zum Beispiel idVendor (= Hersteller-ID)

und idProduct, verwendeter Treiber (Product: CP2102 USB to UART Bridge
Controller).
Für uns ist lediglich die letzte Zeile interessant. Hier steht "cp210x converter now
attached to ttyUSB0", was bedeutet, dass unser Gerät, das tatsächlich mit dem
cp210x-Treiber läuft, am Port ttyUSB0 angeschlossen wurde.
Für Windows gibt es leider kein Terminal-Äquivalent zu dmesg. Jedoch kann man
hier im Hardwaremanager (einfach in die Suche eingeben und mit Enter bestätigen)
unter "USB-Controller" oder "Anschlüsse (COM & LTP)" bei eingestecktem Gerät
sehen, unter welchem Port das Gerät zu erreichen ist. Unter Windows sind USBGeräte an den sogenannten "COM-Port" gebunden.
1.4.2.2 - freeRTOS und das MicroPython Framework
herunterladen
Um das MicroPython Framework herunterzuladen gibt es zwei Möglichkeiten:
Entweder man nutzt das Terminal und lädt sich via Git (ein Tool zur Versionskontrolle
eine Kopie davon auf deinen PC (Git erstellt ein Unterverzeichnis im aktuellen
Arbeitsverzeichnis und kopiert die Inhalte in diesen):
$ git clone
https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo.git
Oder aber man besucht die Seite
https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo
klickt auf den Button „clone or download“ und lädt sich manuell die Zip-Datei
herunter.
Diese muss allerdings anschließend entpackt werden.

1.4.2.3 freeRTOS konfigurieren
Wichtig! Die folgenden Informationen beziehen sich gleichermaßen auf Linux wie auf
MacOS.
Für Windows-Nutzer sind weitere, teils andere Schritte nötig, welche unter folgendem
Link abrufbar sind:
https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo/wiki/winsetup.
Um das Framework zu installieren hat man wieder zwei Möglichkeiten: Über das
Terminal wird mittels cd in das zuvor heruntergeladene Verzeichnis
"MIcroPythonESP32_psRAM", dann ins Unterverzeichnis „MicroPython_BUILD“
gewechselt:
$ cd MicroPythonESP32_psRAM_LoBo/MicroPython_BUILD
Alternativ kann dieser Ordner auch im Filebrowser (Finder oder Dateiexplorer)
geöffnet werden. Unter Linux und MacOS kann das aktuelle Verzeichnis mittels des
Kontextmenüs, das man mit einem Klick auf die rechte Maustaste erreichen kann, im
Terminal geöffnet werden. Klick auf die rechte Maustaste geöffnet werden.
Anschließend geht es an die Kernel-Settings.
Dies hört sich nun aber dramatischer an als es tatsächlich ist!
Eigentlich ist in diesem Schritt nicht allzu viel zu erledigen, jedoch müssen wir kurz
die menuconfig aufrufen (Ein kleines Programm in dem man Kernel-Einstellungen
des Kernels vornehme kann), um eine Datei namens "sdkconfig" zu erstellen.
$ ./BUILD.sh menuconfig
Mit diesem Befehl öffnest sich die menuconfig, in der man nun verschiedene
Einstellungen bezüglich Hardware (.z.B. Bluetooth aktivieren / deaktivieren),
Betriebssystem (Nutzung des zweiten Prozessorkerns gestatten oder nicht) und dem
genutzten Framework (wichtige Module einbinden, unwichtige ausschließen)
vornehmen kann.
Hier navigiert man ausschließlich mit den Cursor-Tasten.
Oben/Unten um die verschiedenen Menüpunkte auszuwählen,
Rechts/Links um in der Menüleiste unten zwischen "select" (=Menüpunkt auswählen
/ ändert),
"exit" ( =zurück zum übergeordneten Menüpunkt oder - falls keiner mehr darüber liegt
- Programm beenden),
"help" (Info zur Verwendung oder zum Wertebereich der Variablen einblenden), "save"
(aktuelle Konfiguration (unter einem bestimmten Namen) abspeichern) oder "load" (=
gespeicherte Konfigurationsdatei laden) auszuwählen.

Sich hier etwas umzusehen schadet nicht, allerdings sollte man sich von den vielen
Begriffen nicht allzu sehr verwirren lassen, da nur einige wenige Änderungen wirklich
notwendig sind.
Wenn also „select“ und der Menüpunkt "MicroPython" markiert sind und auf Enter
gedrückt wird, gelangt man ins Untermenü von MicroPython.
Wenn man nun wieder zurück - also eine Ebene Höher - wechseln möchte, navigiert
man mit den Pfeilen einmal nach rechts, sodass "exit" markiert wird und bestätigt mit
Enter.
Wichtige Änderungen in der menuconfig:
USB Port prüfen:
Serial flasher config → default serial port
Hier sollte geprüft werden, ob der zuvor ermittelte USB Port (ttyUSB0) als default
serial port gesetzt ist.
Webserver-Funktion aktivieren:
Micropython → SystemSettings → Enable Web server
Mit dem Cursor auf diesen Menüpunkt navigieren, auf „y“ für „yes“ drücken, um den
Webserver zu aktivieren. Ein aktiviertes Modul wird mit einem "*" gekennzeichnet. „n“
für „no“ deaktiviert Module, die ggf. versehentlich aktiviert wurden oder einfach nicht
gebraucht werden.
Telnet- und FTP-Server-Funktion mit "n" deaktivieren, falls diese aktiviert sind:
Micropython → SystemSettings → Enable Telnet server
Micropython → SystemSettings → Enable Ftp server
Außerdem müssen wir dafür sorgen, dass wir den vollen Speicherbereich nutzen
können:
MicroPython → System settings → MicroPython heap size (KB)
Hier sollte in der Klammer vor "MicroPython Heap Size" der Wert 96 stehen. Falls
dies nicht der Fall ist, kann mit Enter bestätigt werden um diesen Wert anzupassen.
Das Webserver-Modul benötigt das Modul namens „Websockets“, das ebenso
aktiviert werden sollte:
Micropython → Modules → Websockets

Also: Mit dem Cursor einmal nach rechts, sodass „exit“ markiert wird, mit Enter
bestätigen, einmal nach unten um auf Modules zu gelangen und ebenso mit Enter
bestätigen. Hier einmal „use Websockets“ mit y auswählen, falls dieses noch nicht
aktiviert war.
Zuletzt in der unteren Menüleiste mit Pfeil-Rechts Taste zu "save" wechseln und mit
Enter bestätigen. Es erscheint noch eine Abfrage, unter welchem Dateinamen die
sdkconfig gespeichert werden soll, hier bitte auch nur mit Enter bestätigen.
Anschließend 3x exit wählen um die menuconfig zu beenden.
Falls hier etwas bei der Ausführung schief gehen sollte oder Unsicherheiten
bestehen, kann die Datei „sdkconfig“ ebenso aus dem online-Repository (so etwas
wie ein online-Verzeichnis) zu diesem Projekt auf Github geladen werden. Den Link
zum Repository gibt es allerdings erst im Kapitel "Prototypenbau". Falls die sdkconfig
vom Repository genutzt wird, muss diese in der menuconfig zuerst mit "load"
geladen werden, bevor die menuconfig wieder gespeichert und beendet werden kann
Jetzt geht‘s an die binary!
$ .BUILD.sh clean
$ ./BUILD.sh
eingeben … warten … erledigt!
Neugierig, was hier passiert?
Die Binary-File ist eine ausführbare Binärdatei, die mit einem Build-Prozess erstellt
wird. Ein Build-Prozess ist der Vorgang in der Softwareentwicklung, bei dem eine
Anwendung, bestehend aus Binärcode (binary) aus dem Quellcode erzeugt wird.
Diesen Prozess nennt man auch "kompilieren".
Kompiliert wird mit einem sogenannten Compiler.

1.4.3 – Nützliches Wissen
1.4.3.1 - Compiler
Compiler sind spezielle Übersetzer, die Programmcode aus problemorientierten
Programmiersprachen (auch Hochsprachen genannt) in ausführbaren
Maschinencode (also die Sprache, die die Prozessoren verstehen) übersetzen.
Mit einem Blick in die Ordnerstruktur des Micropython Frameworks findet man zum
Beispiel im Ordner 'MicroPython_ESP32_psRAM_LoBoNeu/
MicroPython_BUILD/components/micropython/esp32' einige Dateien mit der Endung

*.c oder *.h. Dies sind Module, geschrieben in der Programmiersprache C - und die
zugehörigen Header-Files.
Diese sind für uns Menschen noch einigermaßen lesbar. Der Prozessor, der dies
allerdings ausführen muss, kann noch gar nichts damit anfangen. Dieser "versteht"
nämlich nur den sogenannten Maschinencode, bestehend aus einer Folge von Bytes,
die sowohl Befehle als auch Daten repräsentieren.
Da dieser Code für den Menschen kaum lesbar ist, werden in der Assemblersprache
zum Beispiel (eine Sprache, die der Maschinensprache noch am Nächsten kommt)
die Befehle durch besser verständliche Abkürzungen, sogenannte Mnemonics,
dargestellt.
Der Compiler scannt also während des Kompiliervorgangs den Code und übersetzt
ihn - so ressourcenschonend es geht - in Maschinensprache. Die dadurch
entstandene binary lässt sich dann vom Computer ausführen.
Nachfolgend ein kleines Beispiel, wie es für den Prozessor aussehen kann, wenn
man der Variablen "a" einen Wert zuweist oder wenn man zwei Werte, die in Variablen
gespeichert sind, miteinander addiert.
Wichtig zu wissen ist hierbei, dass die Variablen in sogenannten Registern
gespeichert werden. Dies sind Speicherbereiche innerhalb des Prozessors, die direkt
mit dem Rechenkern verbunden sind.
Für neugierige hier einige Links mit detaillierteren Informationen:
https://de.wikipedia.org/wiki/Compiler
https://de.wikipedia.org/wiki/Maschinensprache
https://de.wikipedia.org/wiki/Register_(Computer
)

Maschinencode

Assemblercode

C-Code

Erklärung

(Hexadezimal)
C7 45 FC 02

mov DWORD PTR [rbp-4], 2 int a = 2;

Setze Variable a,
die durch
Register RBP
adressiert wird,
auf den Wert 2.

8B 45 F8

mov eax, DWORD PTR
[rbp-8]

8B 55 FC

mov edx, DWORD PTR
[rbp-4]

01 D0

add eax, edx

89 45 F4

mov DWORD PTR [rbp12], eax

int c = a + b;

Setze Register
EAX auf den Wert
von Variable b.
Setze Register
EDX auf den Wert
von Variable a.
Addiere den Wert
von EDX zum
Wert von EAX.
Setze Variable c,
die durch RBP
adressiert wird,
auf den Wert von
EAX.

1.4.3.2 - Interpreter
Im Gegensatz zu einem Compiler, der vor Ausführung des Programms den
vorhandenen Code in Maschinensprache übersetzt, macht dies ein sogenannter
Interpreter erst während der Laufzeit.
Sprich es wird eine Zeile nach der anderen zuerst eingelesen und dann in
Maschinensprache übersetzt. Nach diesem Schritt erst können die Befehle dem
Prozessor zur Ausführung weitergegeben werden.
Ein Vorteil hiervon ist, dass diese Programme auf verschiedenen Systemen mit
verschiedenen Prozessorarchitekturen ausführbar sind. Leider muss man aber bedingt durch die Übersetzung während der Laufzeit, mit Performanceeinbußen
rechnen. Das bedeutet, dass das Programm allgemein langsamer läuft als selbes
Programm, das in einer kompilierten Sprache geschrieben wird.
Dies wird meist bei der Ausführung von rechenintensiven Anwendungen deutlich
spürbar.

1.4.4 – ESP32 mit Computer und LED Strip verbinden
1.4.4.1 - Framework "flashen"
Genug des Ausflugs in die Theorie. nun wird es Zeit, den ESP auszupacken und
anzuschließen!
Also. Raus aus der Verpackung, ran ans USB-Kabel und im Computer einstecken!
Wie bereits erwähnt wird jedes USB-Gerät vom PC an einen speziellen Port geknüpft,
mit dem es vom System angesprochen wird.
Unter Linux ist dies meist ttyUSB0 oder ttyUSB1.
Sobald der Controller eingesteckt ist, kannst du dies auch einfach prüfen:
$ ls dev/ttyUSB*
ls bedeutet list, sprich es listet nun alle angeschlossenen Geräte, die mit ttyUSB
anfangen. Der * signalisiert, dass nach dieser Zeichenfolge noch andere Zeichen
folgen
Bevor wir die Binary drauf spielen können, löschen wir vorsichtshalber alles:
$ esptool.py --port /dev/ttyUSB0 erase_flash
oder:
$ ./BUILD.sh erase
Und spielen dann die binary auf den Controller:
$ ./BUILD.sh flash

Neugierig? Erster Test??
1.4.4.2 - LED Strip an ESP32 anschließen
Also.
Du hast den LED-Strip, dieser hat an jedem Ende drei
Anschlüsse, einen für 5V (rot), einen Data-Pin (meistens
gelb) und einen für Ground, oder GND (schwarz oder
weiß).
Schließe hier das rote und das schwarze (oder weiße)
Kabel an eine externe Stromquelle (falls es nur wenige
LED sind, kannst du auch den 5V Ausgang und GND vom
ESP nutzen. Allerdings kann dies bei Überspannung
deinen USB-Port am Rechner zumindest vorübergehend
außer Gefecht setzen). Wichtig: um den Stromkreis zu
schließen muss der GND der externen Stromquelle mit
dem GND des ESP verbunden sein. Anschließend
verbindest Du den Data-Pin des LED-Strips (DIN oder DI) mit Pin 14 am ESP.
Der Controller wird - falls noch nicht geschehen - per USB-Kabel mit dem Computer
verbunden.

1.4.4.3 - Eine Verbindung zum ESP herstellen - rshell und REPL
Hierfür verwenden wir das Tool "rshell" (= remote shell), das es uns ermöglicht, eine
Verbindung zum Controller herzustellen um Dateien und Ordner hochzuladen,
auszulesen, zu verschieben oder zu löschen, oder REPL zu starten.
Rshell benötigt einige Angaben, wie zum Beispiel die Puffergröße (--buffer-size=30)
und den Port (-p /dev/ttyUSB0), wobei hier der vorher ermittelte Port (/dev/ttyUSB0)
anzugeben ist.
Beendet werden kann rshell mit der Tastenkombination STRG+C
repl (= Read–eval–print loop) wiederum ist dazu da, um per Kommandozeile Befehle
auf dem Board auszuführen, kleine Funktionen zu erstellen und diese, oder andere
vorhandene Skripte auszuführen.
$ rshell --buffer-size=30 -p /dev/ttyUSB0 -a -e nano
$ repl

Ab hier verändert sich die
Darstellung der Zeilen!
Im Python-REPL werden
die Zeilenanfänge mit den
Zeichen ">>>" signalisiert.
So weiß man sofort, dass
man sich nicht in der
systemeigenen Shell, sondern in der Python-Shell befindet, hier also alle Befehle in
Python geschrieben werden. Beendet wird REPL mit der Tastenkombination STRG+X

2.

– LED Animationen mit Micropython

MicroPython ist - wie bereits erwähnt - eine etwas "abgespeckte" Version von Python
(Version 3). Daher kann man sich auf die Beschreibungen, die sich auf Python
beziehen, nur teilweise verlassen. Jedoch bieten sie gute Beschreibungen in Sachen
Aufbau, Verwendung der einzelnen Module und Funktionen sowie der gängigen
Programmier-Routinen, weshalb öfter auf Seiten verwiesen wird, die eigentlich
Tutorials / Anleitungen für Python 3 bereitstellen:
Python4Kids, sowie deren Unterseite "Denken wie ein Informatiker" bietet sich
speziell für jüngere Generationen an:
http://python4kids.net
http://python4kids.net/how2think/index.htm
Auch auf der deutschen Seite python-kurs.eu findet sich ein sehr schön
beschriebenes und durch simple Grafiken unterstütztes Tutorial für Python 3:
https://www.python-kurs.eu/
python3_kurs.php

2.1 - Module
Modulare Programmierung ist eine Software-Design-Technik, die auf dem
allgemeinen Prinzip des modularen Designs beruht. Unter modularem Design
versteht man, dass man ein komplexes System in kleinere selbständige Einheiten
oder Komponenten zerlegt. Diese Komponenten bezeichnet man üblicherweise als
Module. Ein Modul kann unabhängig vom Gesamtsystem erzeugt und separat
getestet werden. In den meisten Fällen kann man ein Modul auch in anderen
Systemen verwenden.

https://www.python-kurs.eu/python3_modularisierung.php
Um ein Modul nutzen zu können muss es mit dem "import"-Befehl eingebunden
werden. Mehr hierzu später.

2.1.1 - Das machine-Modul
Um in Micropython unter anderem die Pins anzusteuern zu können benötigt man das
Modul "machine". Eigentlich handelt es sich bei "machine" um ein Modul, das sogar
weitere Module, beziehungsweise sogenannte "Klassen" beinhaltet.
https://www.python-kurs.eu/klassen.php
https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo/wiki/machin
e
mit dem machine-Modul kann man zum Beispiel die Prozessorgeschwindigkeit in
MHz auslesen oder einstellen (machine.freq([speed in MHz])) oder den Controller
neu starten (machine.reset()).
Außerdem beinhaltet es das für uns wichtige Modul, bzw. die Klasse "Neopixel", mit
dem wir den Strip konfigurieren und ansteuern, also zum Leuchten bringen können.

2.1.2 - Das neopixel-Modul
Bevor wir den Strip leuchten lassen können, müssen wir also zuerst das Modul
namens machine einbinden und damit den Pin definieren, an den wir den LED-Strip
angeschlossen haben. Anschließend muss ein Objekt von "Neopixel" erzeugt werden.
Im Fachjargon nennt man das "eine Instanz einer Klasse" erstellen. Aber da das
Prinzip der Klassen für Anfänger oft schwer nachvollziehbar ist, lassen wir dieses
Detail einmal aus. Gerne darf aber bei Interesse nachgelesen werden. Das
deutschsprachige Python-Tutorial bietet dazu eine einfach geschriebene und
detaillierte Erklärung:
https://www.python-kurs.eu/python3_klassen.php.
mit dem "import" - Befehl sind wir in der Lage, Module einzubinden.
Auch wichtig für den Umgang mit der Neopixel-Library ist die Modulbeschreibung
des Entwicklers:
https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo/wiki/neopixel



machine.Pin([pin_number], [input / output]) definiert den Pin, den wir in
[pin_number] angeben, sowie ob hier Signale empfangen (input) oder
gesendet (output) werden.



machine.Pin.OUT gibt zum Beispiel an, dass hier Signale vom Controller aus
(an den LED-Strip) gesendet werden.

Gib nun folgende Befehle nacheinander in REPL ein und bestätige sie jeweils mit
Enter:
>>> import machine
>>> led_pin = machine.Pin(14, machine.Pin.OUT)

Um ein Objekt "Neopixel" zu erstellen, müssen ebenso einige Angaben gemacht
werden. So muss der Pin angegeben werden, an dem der Strip angeschlossen ist,
sowie die Anzahl der Pixel auf den Strip (in meinem Fall sind das 14 Pixel). Auch der
verwendete LED-Typ sollte angegeben werden, sofern du auch den Strip vom Typ
"SK6812". Wenn man dies auslässt ist als Standard-Typ TYPE_RGB angegeben, also
LEDs mit jeweils drei Farben: rot, blau grün. Der SK6812 besteht sogenannten
"RGBW" LED, sprich ein Pixel besteht aus vier Farben. Neben RGB auch weiß, so sind
die LEDs auch einzeln dimmbar. So setzt sich dieser Schritt nun zusammen:
machine.Neopixel(pin, num_pixels[, type])



Den Pin haben wir ja bereits im letzten Schritt in einer Variablen namens
"led_pin" gespeichert.
Es bietet sich an, auch die Pixelanzahl (hier: 14) in einer Variablen
(num_pixels) zu speichern. Warum dies sinnvoll ist, wirst du in einem späteren
Kapitel erfahren.

Gib nun folgende Befehle ein, um die Anzahl der Pixel in der Variablen "num_pixels"
zu speichern und eine Instanz der Klasse Neopixel in der Variablen "strip" zu
speichern:

>>> num_pixels = 14
>>> led_pin = machine.Pin(14, machine.Pin.OUT)
>>> strip = machine.Neopixel(led_pin, num_pixels,
machine.Neopixel.TYPE_RGBW)

Selbstverständlich kann man dies auch auf zwei Zeilen, bzw. zwei Befehle
komprimieren, allerdings wirst du gleich sehen, dass dies vergleichsweise sehr
unübersichtlich ist:
> strip = machine.Neopixel(machine.Pin(14,
machine.Pin.OUT), 11, machine.Neopixel.TYPE_RGBW)

2.2 - LEDs leuchten lassen
Neopixel.set( pos, color [, white, num, update] ) ist dafür zuständig, die Pixels
leuchten zu lassen. Auch hier sind zwei Angaben zwingend erforderlich, alle anderen
(in der eckigen Klammer) optional.



pos bezeichnet den Index des Pixels, dessen Farbwert gesetzt werden soll.
color gibt - wie der Name schon sagt - an, in welcher Farbe der Pixel leuchten
soll. Hier kann man entweder vordefinierte Farben verwenden (wie im
folgenden Beispiel), oder Farbwerte als Hexadezimalwert (wird später noch
erklärt)



white (optional) gibt - bei RGBW-Strips - an, wie hell die Farbe Weiß in diesem
Pixel leuchten soll. Wird hier nichts angegeben, wird als Standardwert 0
gesetzt. Hier sind Werte von 0-255 möglich. Je höher, desto mehr weiß wird in
die Farbe gemischt.
•num (optional) definiert, wie viele Pixel in diesem Schritt gesetzt werden
sollen, ausgehend vom Pixel, der in pos angegeben wurde. Standardwert ist
her 1, also wird standardgemäß nur der eine Pixel gesteuert, der in num
angegeben wurde.





•update (optional) bestimmt, ob der oder die Pixel sofort in der jeweiligen
Farbe angezeigt werden soll, bzw. sollen (was auch Standardwert ist), oder ob
mit der Anzeige gewartet werden soll. Falls abgewartet werden sollte, ist
update=False anzugeben.

Um dann allerdings die Pixel in der vorher gesetzten Farbe leuchten zu lassen, ist
zusätzlich noch der Befehl strip.show() nötig.
>>> strip.set(0, strip.NAVY)
lässt also den ersten Pixel des Strips in der Farbe "Navy" leuchten, während

>> strip.set(0, strip.NAVY, num=num_pixels)
alle Pixel in der Farbe "Navy" leuchten lassen

2.2.1 - Farben
Wie bereits erwähnt kann man die Farbe aus vordefinierten Farben oder als
Hexadezimalwert angeben.
Um herauszufinden, welche Farben verfügbar sind, gibt es einen kleinen Trick:
Gib einfach "strip." in REPL ein und drücke einmal auf TAB.
So erscheint eine Auflistung aller vordefinierten Werte und sogar auch der

vorhandenen Funktionen. Die vordefinierten Werte sind konstante Werte und werden
in Großbuchstaben angezeigt. So sind folgende Farbwerte bereits definiert:
BLACK, WHITE, RED, LIME, BLUE, YELLOW, CYAN, MAGENTA, SILVER, GRAY,
MAROON, OLIVE, GREEN, PURPLE, TEAL, NAVY.
Alternativ hat man die Möglichkeit, die Farbe anhand des Hexadezimalwerts
anzugeben.
RGB-Farben werden definiert durch einen Wert für rot (=r), einen für grün (=g) und
einen für blau (=b). Dieser Wert darf zwischen 0 und 255 liegen. Für die Farbe rot
muss also für r=255, sprich höchster Wert, für g=0, und b=0 gesetzt werden. Anders
geschrieben: (255, 0, 0). Dies ist der RGB-Wert als Tupel dargestellt.
Der Hexadezimalwert wäre für rot "#FF0000", bzw. 0xFF0000, wobei die Zeichen "#"
oder "0x" nur vorangestellt werden, um zu zeigen, dass es sich hierbei um einen
Hexadezimalwert handelt. Da "#FF0000" mit der '#' als initiales Zeichen, eine
Zeichenkette darstellt, 0xff0000 hingegen einen (hexadezimalen) Zahlenwert und
sich mit Zahlen besser rechnen lässt, verwenden wir hier die Darstellung 0xff0000.

Wandeln wir die Dezimalzahl "255" in eine Hexadezimalzahl erhalten wir "FF". Der
Hexadezimalwert eines Farbwerts ist also nichts weiter, als die jeweiligen Farbwerte
für r, g und b, einzeln konvertiert in eine Hexadezimalzahl und anschließend
aneinandergereiht.
Die Farbe "aquamarin" hat zum Beispiel den RGB-Wert (127,255,212). Konvertieren
wir die einzelnen Werte in Hexadezimalwerte erhalten wir 0x7FFFD4 (also 127 =
0x7F, 255 = 0xFF, 212 = 0xD4. Konvertieren wir wiederum den kompletten
Hexadezimalwert in einen Dezimalwert erhalten wir 8388564. Beide Werte sind hier
als Farbangabe zulässig.
Die Konvertierung von Dezimal- in Hexadezimalwerte übernimmt Python für uns.
Hierfür gibt es Funktionen, die bestimmte Werte in Werte eines anderen Typs (wie
zum Beispiel Dezimalzahl in Hexadezimalzahl: hex(decimal_value) und andersrum:
int(hex_value) konvertiert. Dies kann gleich in REPL getestet werden:
>>> int(0xff0000)
16711680
>>>
>>> int(0xff)
255
>>> hex(128)
'0x80'
>>>
Möchten wir jedoch ein RGB-Tupel, also hier (127, 255, 212) als Angabe verwenden,
müssen wir dieses erst in den entsprechenden Hexadezimalwert konvertieren.
Wollen wir die Farbe Schwarz, was im additiven Farbsystem, also dem Farbsystem
unseres sichtbaren Lichts soviel wie "kein Licht" bedeutet, müssen wir einfach alle
Farben auf 0 setzen. Dies geht indem man für color den Wert "0x00" angibt. also 0.

2.3 Variablen undDatentypen
Bisher haben wir nur ganz allgemein über Variable gesprochen. Variablen werden
genutzt, um veränderbare Objekte zu speichern. Objekte können zum Beispiel ganz
einfache Zahlenwerte oder etwa Zeichen(ketten) sein. Oder einfach nur den Wert
'True' oder 'False' annehmen

2.3.1 - Variablen in Python
Während in anderen Sprachen der Typ der Variable explizit definiert werden muss
und auch nicht während der Laufzeit verändert werden kann, geschieht das in Python
mit der Zuweisung. Zum Beispiel ist die Variable a = 10 automatisch eine IntegerZahl, also eine ganze Zahl. Wenn man später aber a mit a = 2.3 überschreibt, ist a
vom typ float., also eine Gleitkommazahl.
Man kann nicht nur einzelne Zahlenwerte oder Zeichen(ketten) in Variablen
speichern.
Man hat ebenso die Möglichkeit, eine Liste, ein Tupel oder einen Dictionary in eine
Variable speichern.
Nicht nur das. Man kann ebenso eine Liste aus Listen anlegen. Oder aber ganze
Funktonen in Variablen speichern!
Diese Eigenschaft machen wir uns in einem späteren Kapitel zunutze.

2.3.2 - Datentypen in Python
2.3.2.1 Primitive Datentypen
Boolean ist der einfachste, auch kleinste Datentyp. Dieser kann lediglich zwei
verschiedene Werte, nennen wir es lieber Zustände, annehmen: True oder False. Da
dies ebenso mit 1 oder 0 ausgedrückt werden kann, genügt diesem Zustand ein
Speicherplatz von genau einem Byte (was ebenso nur entweder auf 1 oder auf 0
gesetzt werden kann) Hierbei handelt es sich um einen logischen Datentyp.
Integer bezeichnen ganze Zahlenwerte. Diese können für eine 32-bit integer
entweder nur positive Werte (unsigned integer) von 0 bis 4.294.967.295 annehmen,
oder sowohl positive als auch negative Werte (signed integer) von −2.147.483.648
bis 2.147.483.647
Float oder double bezeichnet Gleitkommawerte. Je nach Größe dieser Variablen (32
oder 64 Bit) kann sie Werte im Bereich 1,5·10^−45 bis 3,4·10^38 (float, 32 Bit) oder im
Bereich 5,0·10^−324 bis 1,7·10^308 (double, 64 Bit) annehmen.

Char, oder character (= Zeichen) bezeichnet ein einzelnes Zeichen, das sowohl
Buchstaben als auch Zahlen, sowie Sonderzeichen beinhalten kann. Codiert werden
diese allerdings als Ganzzahl. Dies mag im ersten Moment seltsam klingen,
allerdings nur, bis man sich anschaut, wie diese Codierung funktioniert. Dies wird
durch eine ASCII-Tabelle klar, die man zum Beispiel bei Wikipedia sehen kann:
https://upload.wikimedia.org/wikipedia/commons/2/26/Asciicodes-table.png
Wie man sehen kann gibt es für jeden Buchstaben, ob klein oder groß, jede Zahl und
jedes Sonderzeichen auf der Tastatur einen zugehörigen numerischen Wert. Anhand
diesem ist man sogar in der Lage, einfache Textverschlüsselungen zu bauen wirklich sicher sind diese Verschlüsselungen allerdings nicht!
2.3.2.2 - Sequentielle Datentypen in Python
Einen sequentiellen Datentypen bezeichnet man Datentypen, die eine Folge an
gleichen oder verschiedenen Elementen beinhaltet.
String bezeichnet eine Zeichenkette, also eine Aneinanderkettung von Zeichen - oder
Sequenz von einzelnen Zeichen. Meist ist dies ein einfacher Text. Jedes einzelne
Zeichen kann über seinen Index angesprochen werden. So ist es auch möglich,
neben ganzen Worten nach dem Vorkommen von Buchstaben oder
Buchstabensequenzen zu suchen, den String anhand dieser zu teilen oder aber
einzelne Buchstaben(Sequenzen) aus dem String zu entfernen. Mit den Strings
haben wir allerdings bereits das Feld der primitiven Datentypen verlassen.
List kann man als eine Art Stapelspeicher bezeichnen, auf den man weitere Elemente
ablegen oder wieder herausnehmen kann. Diese wird gekennzeichnet durch eckige
Klammern []. Man kann mittels des Index auf die einzelnen Elemente der Liste
zugreifen und diese ggf. auch verändern.
Tuple, oder auch Tupel sind einer Liste sehr ähnlich. Man kann ebenso mittels dem
Index auf die einzelnen Elemente zugreifen. Jedoch werden Tupel in runde Klammern
() gepackt und sind im Gegensatz zu Listen nicht veränderbar.
Dictionaries enthalten zusätzlich zu den Werten auch sogenannte Bezeichner, über
den man das Objekt im Dictionary aufrufen kann: capital_dict = {"BadenWuerttemberg":"Stuttgart", "Bayern":"Muenchen", ... }
Ein Dictionary ist allerdings nicht mehr als sequentieller Datentyp einzuordnen. Hier
kann man Schlüssel auf Objekte "abbilden", daher gehört ein Dictionary unter die
Kategorie "Mapping", die wir hier allerdings nicht weiter beleuchten weden.
Mehr Informationen zu den bisher gezeigten Datentypen kann man auf folgenden
Links abrufen:

https://www.python-kurs.eu/python3_variablen.php
https://www.python-kurs.eu/python3_sequentielle_datentypen.ph
p
https://www.python-kurs.eu/python3_listen.php
https://www.python-kurs.eu/python3_dictionaries.php

2.4 – Einfache Animation
2.4.1 - Blinken
Bevor die LEDs allerdings blinken können, muss man sich Gedanken darüber
machen, was hier eigentlich genau geschehen soll:
Die LEDs werden alle zur gleichen Zeit eingeschalten. Sie leuchten eine Weile,
anschließend werden sie ausgeschalten. Nach einer Weile gehen sie wieder an, und
so weiter.
Wenn die LEDs also zweimal blinken sollen, kann man das – ganz einfach mal in
folgende Befehlsfolge zusammenfassen:
mach alle an
warte
mach alle aus
warte
mach alle an
warte
mach alle aus.
Nur wäre diese Herangehensweise auf Dauer sehr umständlich. Wollten wir die LEDs
auf diese Weise 1000x blinken lassen, hätten wir mittels copy + paste einiges zu tun!
Außerdem würde das Programm viel zu groß und unübersichtlich werden!
Viel besser wäre es doch wenn wir sagen könnten:
mach bitte 5x:
mach alle an
warte
mach alle aus
warte.

2.5 - Schleifen
Aus diesem Grund gibt es Schleifen.
Mit Schleifen kann man zum Beispiel festlegen, dass bestimmte Codezeilen genau 5,
10, 1000 Mal ausgeführt werden sollen (for-Schleife). Außerdem kann man auch
sagen, dass ein Code-Block, wie bei uns das Blinken, nur ausgeführt werden soll,
während es draußen dunkel ist (while-Schleife).
Zum Beispiel: während es draußen dunkel ist: blinke, checke ob es immer noch
dunkel ist. wenn dunkel: nochmal das ganze! Wenn hell, abbrechen.

Auch könnte man mit einer while-Schleife sagen: mache das einfach immer wieder!
Eine sogenannte Endlos-Schleife.
Grundlegend braucht eine Schleife für jeden Schleifendurchlauf eine Bedingung,
deren Wahrheitsgehalt geprüft werden muss. Trift die Bedingung zu, wird von dieser
Bedingung der Boolsche Wert True zurück gegeben, was bedeutet, die
Schleifenbedingung trift zu und sie wird ein weiteres mal ausgeführt. Trift die
Bedingung nicht zu, wird der Wert False zurückgegeben. Die Schleifenbedingung trift
nicht zu und die Ausführung des Codes geht in der ersten Zeile nach der Schleife
weiter.

2.5.1. - For-Schleife
https://www.python-kurs.eu/python3_for-schleife.php
Um zu sagen: führe diesen Codeblock 5x aus schreibt man in Python:
>>> for i in range(5):
range(5) ist eine Funktion, die nacheinander alle ganzen Zahlen von 0 bis 4(!!!)
ausgibt. Genauer sollte man eigentlich schreiben: range(0, 5). Warum 4 und nicht 5?
5 gibt hier das obere Limit der Zahlenfolge, sowie die Anzahl der Zahlen an. Also
bedeutet range(5) (bzw range(0,5)), dass eine Zahlenreihe, bestehend aus 5 Ganzen
Zahlen, beginnend bei 0, endend bei < 5 - also 4, zurückgegeben wird.
Bedeutet wiederum: dieser Codeblock wird 5x ausgeführt. Für jedes i von 0 bis < 5.

Aber - was befindet sich in diesem Codeblock - was außerhalb?
Dies wird in Python durch Einrückungen definiert.

Sprich alles, was hier 5x ausgeführt werden soll, muss nun im Vergleich Zeilenanfang
der for-Schleife einmal mit TAB eingerückt werden.
REPL erledigt das für uns, sobald man die Zeile "for i in range(5):" mit einem
Doppelpunkt beendet und einmal Enter gedrückt hat. Der Cursor ist nun eingerückt,
zudem hat sich wieder der Zeilenanfang verändert. Hier stehen nun "...":
Auch wenn wir mit Enter in die nächste Zeile wechseln, bleibt dieser noch auf selber
Position.
Um die For-Schleife mit der Range-Funktion mal zu demonstrieren, sagen wir:

für jedes i von 0 bis < 5 :
gib einmal den Wert von i aus.
Um Werte (oder Texte) im Terminal auszugeben nutzt man die Funktion
print([variable / Text (in Anführungszeichen)])
Zweimaliges Drücken der Enter-Taste lässt den Cursor wieder an den Anfang der
Zeile stellen, sprich, falls hier noch etwas gemacht werden sollte, dann wird das erst
ausgeführt, sobald die Schleife verlassen, also 5x ausgeführt wurde.

Da wir nach der Schleife nichts weiter ausführen wollen, können wir nochmals mit
Enter bestätigen, sodass die Schleife ausgeführt werden kann

Dies ging nun allerdings sehr sehr schnell! Wenn wir in diesem Tempo unsere LEDs
blinken lassen würden, würden wir das Blinken sehr wahrscheinlich überhaupt nicht
mehr wirklich wahrnehmen!
Um diesen Prozess zu verlangsamen, müssen wir sogenannte Delays einbauen.
Dies erledigt man am einfachsten mit dem Modul "utime", bzw. dessen Funktion
sleep_ms()
utime.sleep_ms(200) sorgt dafür, dass die nächste Codezeile (oder das nächste Mal,
dass die Schleifenbedingung geprüft wird) erst 200 ms später stattfindet. Wenn nun
folgende Zeilen ins Terminal eingegeben und - wie eben schon mal - 3x mit Enter
bestätigt werden ...
import utime
>>> for i in range(5):
... print(i)

... utime.sleep_ms(200)
... erscheinen nun die Ergebnisse der einzelnen Schleifendurchläufe in zeitlichen
Abständen auf dem Terminal. Dasselbe Verhalten wollen wir für unsere blinkenden
LEDs!

2.5.2 - While-Schleife
https://www.python-kurs.eu/python3_schleifen.php
Die einfachste, wahrscheinlich auch bekannteste While-Schleife ist die
Endlosschleife. Einmal gestartet soll sie endlos den beinhalteten Codeblock
ausführen. Hierzu sind gerade mal zwei Worte notwendig:
while True:
Wollen wir also unsere LEDs endlos blinken lassen müsste die Anweisungsfolge
lauten:
mache für immer:
schalte ein – warte – schalte aus – warte.
Dies würde dann folgendermaßen aussehen:
import utime
>>> while True:
... strip.set(0, strip.TEAL, num=num_pixels)
... utime.sleep_ms(200)
... strip.set(0, 0x00, num=num_pixels)
... utime.sleep_ms(200)
Wir können allerdings ebenso mit einer while-Schleife ausdrücken, dass der
Codeblock nur 5x ausgeführt werden soll.
Nehmen wir an, eine Variable i hat vor Schleifenaufruf den Wert 0. Dann kann man
sagen 'while (i<5)', also solange der Wert von i kleiner ist als 5, wird die Schleife
ausgeführt. Dazu muss aber auch die Variable i innerhalb des nachfolgenden
Codeblocks um 1 erhöht werden, ansonsten kann man sich auch so eine
Endlosschleife generieren!
>>> while (i < 5):
... i += 1
...

Schleifen funktionieren so, dass sie prüfen, ob die Bedingung, oder auch der
Ausdruck nach dem Codewort "while" wahr ist. Wenn dem so ist, darf die Schleife
ausgeführt, bzw. fortgeführt werden, ansonsten wird die Ausführung des Codes nach
der Schleife fortgesetzt.

2.6 - Operatoren und Ausdrücke
Unter einem Ausdruck versteht man in der Programmierung eine Kombination aus
Variablen, Konstanten, Operatoren und Rückgabewerten von Funktionen. Die
Auswertung eines Ausdrucks ergibt einen Wert, der meistens einer Variablen
zugewiesen wird. In Python werden Ausdrücke unter Verwendung der gebräuchlichen
mathematischen Notationen und Symbolen für Operatoren geschrieben.
Hier werden nur die für uns wichtigsten Operatoren genannt. Der arithmetische
Operator ist in dieser Auflistung der einzige Operator, der (fast) immer einen
numerischen Wert (int oder float) zurückgibt, während ein Logischer- oder ein
Vergleichsoperator den Wahrheitsgehalt prüft und lediglich True oder False
zurückgibt.

2.6.1 - Arithmetische Operationen
Operator
+
*
/
//

%
=

Aktion
Addition
Subtraktion
Multiplikation
Division
Truncation Division /
Ganzzahldivision. Das
Ergebnis der Division ist
der ganzzahliger Anteil der
Division. Falls jedoch
mindestens einer der
Operanden ein Float-Wert
ist, so wird der ganzzahlige
Anteil der Division als Float
ausgegeben.
Modulo-Division mit Rest
(nur mit Ganzzahlen)
Zuweisung von Werten

Beispiel
11 = b + c
1=b-c
12 = b * c
5=c/b
>>> 10 // 3
3
>>> 10.0 // 3
3.0

1=b%c
(5*1 = 5, 6-5 = Rest 1)

2.6.2 - Vergleichsoperatoren
>
<
>=
<=
==
!=
in

Größer als
Kleiner als
Größer gleich als
Kleiner gleich als
Gleich
Nicht gleich, ungleich
Element von

1 in [1, 2, 3]

2.6.3 - Logische Operatoren
And
Or
Not

Logisches Und
Logisches Oder
Logisches Nicht

(a or b) and c

Diese Auflistung ist nicht vollständig. Weitere Operatoren und auch Erklärungen
finden sich auf folgenden Seiten:
https://www.python-kurs.eu/operatoren.php
http://python4kids.net/how2think/kap02.htm

Wollen wir nun aber erreichen, dass die LEDs zu verschiedenen Zeiten / in
verschiedenen Abschnitten unseres Programmcodes ausgeführt wird, so gibt es
auch für diesen Fall eine Zusammenfassung.

2.7 - Funktionen
Routinen (Code-Abschnitte), die wiederholt, zu verschiedenen Zeiten oder in
verschiedenen Abschnitten ausgeführt werden sollen, fasst man in sogenannte
Funktionen zusammen.
Funktionen werden mit dem Schlüsselwort "def" (=define) eingeleitet. Der Aufbau
sieht folgendermaßen aus:
def funktions_name(parameterliste):
""" funktion_name(parameterlist)
an example function to show structure of functions.
parameterlist: (type of parameter: Text or number) short
description what parameter(s) is / are needed
returns: (type of returned value) if a value is returned,
describe it shortly
"""
Anweisung(en)
(return)







funktios_name: Wie in Punkt 1 beschrieben sollte der Funktionsname stets
möglichst treffend beschreiben, was mit dieser Funktion bezweckt werden
soll, bzw. was der Code für Auswirkungen hat.
Seite 35
Die Parameterliste kann aus beliebig vielen Parametern bestehen, die benötigt
werden, um die in der Funktion stehenden Anweisungen auszuführen.
Bei dem in grün dargestellten Text handelt es sich um einen "docstring". Diese
werden genutzt, um Funktionen und Module zu beschreiben. Mehr dazu im
Kapitel "Kommentare in Python"
Das return Statement ermöglicht es, Ergebnisse von Berechnungen, oder eben
Werte eines ausgelesenen Sensors, wieder zurück zu geben. Diesen Wert kann
man also mit dem Funktionsaufruf in eine Variable speichern.

Weitere Infos findet man wie Immer im Netz. Zum Beispiel auf:
http://python4kids.net/how2think/kap05.htm
https://www.python-kurs.eu/python3_funktionen.ph
p
https://www.python-kurs.eu/python3_parameter.ph
p
Bevor man eine Funktion implementieren möchte, muss man sich darüber im Klaren
sein:
1. Welche Anweisungen in dieser Funktion gemacht werden - Funktionsname
daher möglichst beschreibend wählen
2. ob oder welche Werte dazu nötig sind, die man ggf. verändern kann
1. ob die Funktion einen Wert zurückgeben muss (wie bei einer Berechnung)
Wollten wir jetzt, als sehr einfaches Beispiel, eine Funktion schreiben, in der zwei
Werte (Parameter genannt) miteinander addiert werden sollen, würden wir wie folgt
vorgehen:
1. Wir wollen eine Funktion, in der zwei Zahlen miteinander addiert werden.
Nennen wir sie also einfach "addieren".
2. Zwei Werte sind dazu nötig. Der erste und der zweite Summand.
3. Die Summe der Addition soll zurückgegeben werden.
def addieren(a, b):
e = a + b
return e
ergebnis = addieren(a, b)
Dies können wir gleich auf unser Beispiel übertragen. So könne wir mit dem
Funktionsaufruf zum Beispiel gleich sagen, in welcher Farbe die LEDs blinken sollen.
Zudem können wir festlegen, wie lange die LEDs leuchten und wie lange sie aus sein
sollen:
1. Wir wollen den LED-Strip blinken lassen. Daher nennen wir die Funktion „blink“
2. Wir möchten die Farbe und die Zeiten (an + aus) verändern. Dazu benötigen
wir also drei Parameter.
3. Da wir lediglich eine Ausgabe an einen Pin machen, brauchen wir keinen
Rückgabewert

In der bisherigen Blink-Funktion wurden festen Werten für die Farbe und die Zeiten
verwendet. Diese müssen nun durch Variablen ersetzen, dessen Wert erst bei Aufruf
der Funktion bestimmt, bzw. übergeben wird.
Auch bei Variablen gilt stets darauf zu achten, treffende Namen zu wählen, um die
Lesbarkeit zu gewährleisten und - ganz wichtig - auch nach einigen Wochen oder
Monaten noch nachvollziehen zu können, was hier genau geschieht.
Für die Farbe bietet es sich daher an, die Variable "color" zu nennen. Für die Zeit, die
die LEDs leuchten sollen, "time_on" und Für die Zeit, die die LEDs nicht leuchten
sollen, "time_off"
>>> def blink(color, time_on, time_off):
... strip.set(0, color, num=num_pixels
... utime.sleep_ms(time_on)
... strip.set(0, 0x00, num=num_pixels)
... utime.sleep_ms(time_off)
Nun ist es ganz einfach möglich, mit den Farbwerten und Blink-Intervalls Zeiten
herumzuspielen.
Nachdem die Funktion in REPL eingetragen und fehlerfrei gespeichert wurde, kann
man sie mit …
>>> blink(strip.RED, 200, 10)
... aufrufen.
Gerne kann anstatt des Standardwerts für color auch mit Farbwerten experimentiert
werden. Im Netz findet sich unter dem englischen Suchbegriff "hex color picker" (=
hexadezimaler Farbwähler) eine einfache Möglichkeit, die gewünschte Farbe als
Hexadezimalwert zu erhalten.
Keine Sorge, falls bei den ersten Versuchen mit Funktionen öfter Fehler passieren.
Dies passiert immer wieder und trägt nur dem Lernprozess bei!

2.7.1 - Funktionen mit optionalen Parametern
Sicherlich ist bereits aufgefallen, dass bei der Funktion strip.set(), im Falle, dass alle
LEDs gleichzeitig geschalten werden sollen, die Anzahl der zu schaltenden LEDs als
Parameter "num= .." übergeben wird.
Hierbei handelt es sich um einen optionalen Parameter, auch Default-Parameter
genannt.
Dies ist sehr praktisch, da wir hierfür einen Standardwert (Default-Wert) nutzen
können, der bei Bedarf verändert werden kann.

Wenn im Vorfeld bereits ansprechende Zeit-Werte durch Versuche ermittelt wurden,
kann man diese ebenso als optionale Parameter angeben:
>>> def blink(color, time_on = 200, time_off = 100):
... strip.set(0, color, num=num_pixels
... utime.sleep_ms(time_on)
... strip.set(0, 0x00, num=num_pixels)
... utime.sleep_ms(time_off)
Zu beachten ist hier lediglich, dass die Default-Parameter grundsätzlich zuletzt
genannt werden, da alle Variablen davor, hier "color", sogenannte
Positionsparameter sind, sprich anhand ihrer Position identifiziert werden.
Die ursprüngliche Fassung der Blink-Funktion bestand nur aus Positionsparametern.
color, time_on, time_off wurden beim Funktionsaufruf in dieser Reihenfolge
eingegeben und auch so zugewiesen.
Da wir nun time_on und time_off als optionale Parameter definiert haben, dürfen
diese genau aus diesem Grund nicht am Anfang stehen. Ansonsten würde die
Gefahr bestehen, dass die Werte falsch zugeordnet werden!
Würden wir nun alle drei Werte nennen, müssten wir die Schlüsselwörter
(time_on / time_off) nicht zwingend mit angeben.
Falls wir aber nur den Wert für time_off ändern wollten, den für time_on aber auf
default lassen würden, können wir time_on also einfach auslassen und die Funktion
mit ...
>>> blink(0x3F03F, time_off=50)
... aufrufen.

Aufgabe: Blinken in zwei Farben
Schaffst du es auch, den Strip anstatt ein- und auszuschalten zwischen zwei Farben
wechseln zu lassen?
Oder abwechselnd in zwei Farben blinken zu lassen?

2.8 - Ein erstes kleines Skript
REPL ist zwar durchaus ein nützliches Tool, jedoch kann es sehr mühsam werden, da
man vieles einfach öfter tippen muss als dies notwendig wäre.
Da unsere Funktionen nun nach und nach immer komplexer werden und es nun
vorteilhaft ist, Codepassagen einfach überarbeiten zu können, steigen wir nun auf
einen Texteditor um.

2.8.1 - Editor
Hierzu würde für den Anfang ein normalen Texteditor nutzen, der standardgemäß auf
jedem Betriebssystem vorinstalliert ist. Allerdings ist es vorteilhaft, einen Editor zu
nutzen, der auch fürs coden gedacht wurde. Sehr beliebt ist zum Beispiel Atom.io,
der kostenlos für alle Systeme verfügbar ist.
Dieser kann auf
https://atom.io/
heruntergeladen werden.
Atom besteht im Wesentlichen aus zwei Bereichen:

Der linke Bereich, "Project" genannt, ist der Bereich für die Projektordner.
Der Rechten Teil bietet Platz für den eigentlichen Editor, wobei man hier auch
mehrere Dateien parallel öffnen kann. Diese sind dann sowohl auf eigenen Reitern
als auch nebeneinander, in mehreren Fenstern, anzusehen.

Um einen Projektordner zu erstellen, bzw. auszuwählen, klickt man einfach mit der
rechten Maustaste in das "Project"-Feld und wählt im Dropdown-Menu "Add Project
Folder".

Falls noch kein separater Ordner für Skripte oder ganze Projekte vorhanden sein
sollte, empfiehlt es sich, gleich einen Ordner hierfür anzulegen.
Ich habe zum Beispiel in meinem Home-Verzeichnis einen Ordner namens "coding" in
dem ich meine Skripte (als einzelne Files) und Projekte (in Ordnern) sortiert nach
Programmiersprache ablege.
Der Name ist frei wählbar, sollte jedoch ebenso beschreibend für das gesamte
Projekt sein.
Möchte man innerhalb des neu erstellten Projektordners eine neue Datei anlegen,
kann man dies, indem man mit der rechten Maustaste das Kontextmenü des
Projektordners öffnet und "new File" wählt. Alternativ kann man bei markiertem
Projektordner die Taste A drücken.
In beiden Fällen öffnet sich ein Eingabefenster, in dem der Name der Datei - Inklusive
Dateiendung (animations.py) eingegeben werden muss.
Im rechten Bereich befindet sich dann der eigentliche Texteditor. Hier wird der Inhalt
der geöffneten (Projekt-)Dateien (in allen gängigen Sprachen) angezeigt.
Außerdem wird hier nun, nach und nach, beginnend mit dem import-Statement, alles,
was wir bisher in REPL erstellt haben, übertragen.
Sehr schnell erkennt man hier die Vorteile:

Durch farbige Hervorhebungen wirkt der Text auf Anhieb viel strukturierter.
So werden zum Beispiel alle Schlüsselwörter (import, def, ...) lila gekennzeichnet. Alle
Funktionen, Konstruktoren sind cyan, sowie Werte orange gekennzeichnet sind.
Auch die Zeilenzahlen am linken Rand werden noch von Vorteil sein, wenn es zu den
ersten Syntaxfehlern (oder manchmal einfach Tippfehlern) kommt.
Nachdem nun alles in den Editor übertragen und gespeichert wurde, müssen wir aber
doch wieder das Terminal nutzen.

Atom.io beinhaltet ebenso ein Terminal, das
praktischerweise beim Start sofort den Projektordner
als Arbeitsverzeichnis öffnet. Diesen erreicht man über
das kleine '+' - Zeichen am linken unteren Rand.
Von hier aus können wir direkt via rshell eine Verbindung zum ESP herstellen:
$ rshell --buffer-size=30 -p /dev/ttyUSB0 -a -e nano
bevor wir aber REPL starten, kopieren wir das erstellte Skript auf den ESP:
$ cp animations.py /flash
Anschließend müssen wir den ESP neu starten. Hierzu einfach den Reset-Button (rst)
auf dem Controller ducken.
Nun können wir den REPL-Mode starten. Also wieder einfach "repl" im Terminal
eingeben und mit Enter bestätigen.
Um nun die Funktion blink zu starten, muss das Skript zuerst importieren.
>>> import animations
Wie unschwer zu übersehen, funktioniert dies auf dieselbe Weise wie auch
vorhandene Module eingefügt werden. Dies bedeutet, dass mit diesen wenigen
Codezeilen bereits ein Mini-Modul erstellt wurde!
Es besteht zwar momentan nur aus einer Funktion, aber die wird nicht sehr lange
alleine bleiben!

Oooops! Schon hat sich ein kleiner 'Fehlerteufel' eingeschlichen. Wer findet den
Fehler?
Aber so wird gleich klar, wozu die Zeilenanzeige im Editor gut ist!
Bei dieser Fehlermeldung sind für uns zuerst einmal die letzten beiden Zeilen
relevant.
In der letzten steht die Art des Fehlers, der gefunden wurde: „invalid syntax“ - also ein
Syntax-Fehler. Die Zeile darüber sagt mir, welche Datei betroffen ist und in welcher
Zeile es zu diesem Fehler gekommen ist - also Zeile 11.
Also werfen wir einen Blick in das erstellte Skript.

utime.sleep_ms(time_on) in Zeile 11 sieht allerdings richtig aus. Kein Syntax-Fehler
zu finden! In diesem Fall sollte man sich auch die darüber liegende Zeile genauer
ansehen.
strip.set(0, color, num=num_pixels in Zeile 10 - hier stimmt definitiv etwas nicht!
Eine Klammer wurde geöffnet, aber nicht wieder geschlossen!
So hat der Interpreter verstanden, dass die darauffolgende Zeile - Zeile 11 - also noch
Bestandteil dessen ist, was in die Klammer soll. Da dies aber so keinen Sinn ergibt,
gab es einen Syntax-Fehler. Und da dieser erst in Zeile 11 als Fehler registriert wurde,
wurde auch Zeile 11 angegeben!
Oft kommt es vor, dass selbst dies nicht hilft, um den Fehler nachvollziehen zu
können. Dann lohnt sich ein Blick auf die Zeilen der Terminalausgabe, die sich über
der letzten beiden befinden. Hier wird dann der sogenannte Backtrace aufgelistet.
Im weiteren Verlauf (von unten nach oben gelesen) stehen dann gegebenenfalls
noch Angaben zu Funktionen / Modulen, die im Zusammenhang mit dem Fehler
aufgerufen wurden.
Sprich jede Funktion oder jedes Modul, das im Zusammenhang mit dieser Codezeile
steht, wird der Reihe nach aufgelistet. So kann man Schritt für Schritt verfolgen, was
passiert ist und somit im Idealfall ermitteln, was zu diesem Fehler geführt hat.
Debugging nennt man diesen Prozess, und die hier gezeigte Methode ist relativ
simpel - allerdings auch nur geeignet, um kleine, überschaubare Programme zu
debuggen.
Ein kleiner Scherz am Rande: Debugging is like being the detective in a crime movie
where you are also the murderer.
Dieser Prozess kann sehr nervenaufreibend sein, und jede(r) kann immer wieder an
den Punkt kommen, an dem sie / er sein Können in Zweifel stellt.
Dies ist aber relativ normal! Und: Es geht vorbei! Irgendwann findet man den - oft viel
zu banalen - Fehler und prompt geht es weiter!
Also. Im Editor Klammer einfügen. Skript speichern. Im Terminal mit STRG +X REPL
beenden. Skript erneut kopieren und REPL wieder starten. ESP neu starten.
Tipp für "faule": Man kann diese Befehle auch auf eine Zeile komprimieren! In Linux
ist die Systemsprache C. Sprich Linux wurde komplett in C geschrieben. In fast allen
Programmiersprachen (außer Python) werden die Zeilenenden mit einem ";"
versehen, um dem Compiler oder dem Interpreter zu signalisieren, dass der Befehl
hier nun zu Ende ist. Selbes kann man sich auch im Terminal zunutze machen, indem
der Befehl zum Kopieren und der um REPL zu starten auf eine Zeile - getrennt durch
einen ';' - geschrieben wird:
$ cp animations.py; repl
Auch wenn in Python keine Semikolons nötig sind um die Zeile (oder den Befehl) zu
beenden, funktioniert selbe Methode auch im REPL-Mode!

Da wir wieder den Controller neu starten müssen, können wir - anstatt den Button zu
drücken - hier einfach eingeben:
>>> import machine; machine.reset()
Nun sollte das Importieren problemlos funktionieren.
Übrigens: Wenn der ESP nicht gerade außer Betrieb (= ohne Strom) war, muss man
eventuell das machine-Modul nicht erneut einbinden.
Dies lässt sich testen, indem man "ma" in REPL eingibt und anschließend die TABTaste drückt. Sollte das Modul bereits verfügbar sein, wird die Autovervollständigung
den Rest des Wortes "machine" einfügen.

2.8.2 - Skript auf dem ESP ausführen
Eine Funktion in einem Modul wird aufgerufen, indem man zuerst den Modulnamen
schreibt, anschließend, durch einen Punkt getrennt, der Funktionsnamen inkl.
Parameter in der Klammer:
>>> import animations
>>> animations.blink(0x00ff00)
Nun haben wir zwar eine Blink-Funktion, diese lässt die LED allerdings wieder nur 1x
blinken!

2.8.3 - Aufgabe: Schleifen erstellen
Erstelle nun anhand der vorherigen Beispiele eine Blink-Funktion, die
10 x blinkt (einmal mit for- und einmal mit while-Schleife)
für immer blinken wird.
(Special) erweitere die Funktion (Parameterliste und Funktionsrumpf) so, dass beim
Funktionsaufruf definiert werden kann, wie oft die Schleife durchlaufen werden soll.

2.9 - Dateisystem auf dem ESP
Schauen wir uns nun mal die vorhandenen Dateien auf dem ESP an. Dazu müssen
wir zuerst REPL mit STRG+X wieder verlassen um anschließend mit ls /flash den
Inhalt des flash-Speichers auf dem ESP32 ausgeben zu lassen:
>>> ls /flash
gibt uns nun den aktuellen Inhalt des Flash-Speichers auf dem ESP aus.
Neben unserer "animations.py" befindet sich nur eine Datei auf dem Controller:
"boot.py".
Dies ist die erste Datei, die beim Boot-Vorgang (oder einfach beim Starten) des ESP
ausgeführt wird. Hier sollte lediglich Code stehen, der finale Einstellungen vornimmt,
um den Boot-Prozess abzuschließen. Sehr viel passiert hier nicht - und wir sollten die
Datei auch zum jetzigen Zeitpunkt nicht verändern, jedoch darf gerne mal einen Blick
darauf geworfen werden. Damit die Datei auch im Projektordner landet und nicht
unter Umständen irgendwo im lokalen Dateisystem "verloren" geht, muss hier als
zweites Argument (Ziel) der vollständige Pfad zum Projektordner eingegeben
werden:
>>> cp /flash/boot.py
[/path/to/your/projectDirectory]/[projectDir]
>>> cp /flash/boot.py
/home/chrissi/coding/microPy/rainbowWarror
kopiert die Datei vom Controller in dein lokales Filesystem, sodass diese mit jedem
beliebigen Texteditor gelesen werden kann.
Hier wird das Modul "sys" (Abkürzung für System) eingebunden, und der Liste
sys.path (eine Variable des Moduls sys) ein Pfad zu einem Ordner namens 'lib' auf
dem Flash-Speicher als Text zugewiesen.

Um herauszufinden, was hier genau geschieht, lohnt es sich, einen Blick in die
Dokumentation der Micropython-Implementierung zu werfen:
https://docs.micropython.org/en/latest/pyboard/library/sys.html
sys.path: A mutable list of directories to search for imported modules.

Bedeutet, dass hier eine veränderbare Liste an Ordnern in der Variablen gespeichert
wird, in denen nach eingefügten Modulen gesucht wird. Sprich falls wir je weitere
(externe) Module benötigen, die nicht in der Micropython-Implementierung
vorhanden sind, müssen diese im Ordner „flash/lib“ gespeichert werden, andererseits
werden diese wahrscheinlich nicht gefunden.
Nachdem "boot.py" ausgeführt wurde, wird standardgemäß nach einem File namens
"main.py" gesucht. Falls diese vorhanden ist, wird sie ausgeführt.
Nur, was macht main.py?
Dies kommt sehr darauf an, wofür der ESP eingesetzt werden soll! So können hier
weitere Module eingebunden und ausgeführt werden, wie unsere Blink-Funktion.
Daher können wir nun alles in main.py übertragen, was wir nach dem letzten Neustart
in REPL eingegeben haben, sodass die Funktion "blink" automatisch nach dem BootProzess startet.
Im Editor erstellen wir also eine neue Datei namens "main.py", die im Projektordner
gespeichert wird und fügen die zuletzt in REPL ausgeführten Zeilen ein.
In Atom können nicht nur mehrere Dateien gleichzeitig geöffnet sein (Zugriff über die
Reiter), es gibt auch einen sogenannten Split-Mode, mit dem wir zwei Dateien
nebeneinandersetzen können. Dazu klicken wir auf den Reiter "animations.py", halten
die Taste gedrückt und ziehen sie in den rechten Bereich des Fensters. So kann man
beide Dateien nebeneinander betrachten, was gerade beim Schreiben von
Funktionsaufrufen praktisch sein kann.

In der Zwischenzeit wurden die gestellten Aufgaben sicherlich bereits erledigt? Für
den weiteren Verlauf des Projekts ist es gut, wenn wir der Blink-Funktion mitteilen

können, wie oft sie blinken kann. Dazu ist ein weiterer optionaler Parameter
notwendig.
Vorsicht bei der Namensgebung für diesen Parameter! Hier würde sich "range" als
treffender Name anbieten. Jedoch kann dies zu Konflikten führen. Ebenso darf man
keine Schlüsselwörter als Variablennamen einsetzen. Daher nennen wir diese
Variable lieber "count"

2.10 - Animationen Teil 2: running dots
Eine Möglichkeit, mehr Bewegung in die Animation einzubringen, ist es, wenn zum
Beispiel nur eine LED zur gleichen Zeit - jedoch nach und nach um eine Position
verschoben - leuchtet. Nur - wie erreichen wir das?

schalte LED an Pos 0 ein
warte
schalte LED an Pos 0 aus
schalte LED an Pos 1 ein
warte
schalte LED an Pos 1 aus
.......
schalte LED an letzter Pos ein
warte
schalte LED an letzter Pos aus

Hierfür wird also wieder eine for-schleife benötigt, wobei die obere Grenze der rangeFunkton gleich der Anzahl der LED (also num_led) ist. Anders geschrieben lautet
dieser Pseudocode also:
für jedes LED in der range (0, num_led):
schalte LED ein
warte
schalte LED aus
Diese Funktion wird einfach in animations.py eingefügt

def running_dot(color = 0x00FFFF, time_on=50):
while True:
for i in range(num_pixels):
strip.set(i +1, color)
utime.sleep_ms(time_on)
strip.set(i +1, 0x00)
utime.sleep_ms(1)
Nun soll wieder erreicht werden, dass die Farbe und die Zeit, die die LED leuchten
sollen, bestimmt werden können, daher werden color und time_on als optionale
Parameter angegeben. color = 0x00FFFF ergibt die Farbe cyan, mit time_on = 50 ms
läuft die Animation relativ zügig durch. Hier ist am Ende der Schleife ein kurzes Delay
von min 1ms notwendig, da es sonst zu fehlerhaften Zuweisungen auf dem Strip
kommt. Würden wir dieses Delay auslassen, würden die LEDs in allen möglichen
Farben kreuz und quer leuchten!
Hier muss ich auf eine kleine "Besonderheit" in dieser MicroPython Implementierung
hinweisen, die leider nicht den gängigen Programmierkonventionen entspricht!
Normalerweise fängt der Informatiker bei 0 an zu zählen. Von 0 bis 9 hat man genau
10 Ziffern. Und zwar genau die 10 Ziffern, die notwendig sind, um alle Zahlen im 10er
Zahlensystem darzustellen.
Dies bedeutet, dass - normalerweise - das Element, das sich an erster Stelle befindet,
mit dem Index 0 angesprochen wird. Ebenso wird das Element, das sich an Stelle n
befindet, mit dem Index n-1 angesprochen.
Auf den Strip übertragen würde dies bedeuten, die LED an 1. Stelle würde mit pos = 0
angesprochen werden, die LED an der letzten Stelle mit pos = (num_pixels -1)
Dies ist hier aber nicht der Fall! Die erste LED kann man sowohl mit pos = 0 als
auch mit pos = 1 ansprechen. Dagegen wird mit pos = num_pixels-1 die vorletzte
LED leuchten, nicht etwa die letzte. Die letzte LED wird mit num_pixels
angesprochen.
Daher müssen wir in strip.set() als Index grundsätzlich i+1 übergeben!
Gestartet wird die Animation wieder in "main.py". Da alle Parameter optional sind,
muss man auch beim Funktionsaufruf nicht zwingend Parameter übergeben!
animations.running_dots()
... führt die Animation mit Default-Werten aus.

2.11 - Kommentare in (Micro)Python

Nun haben wir eine Situation, in der wir ein Stück Code geschrieben haben, das wir
zeitweise "ausblenden" wollen. Eine Möglichkeit wäre, diese einfach zu entfernen.
Sobald es aber um mehr als nur eine Zeile geht, läuft man in Gefahr, dass dieser
Codeblock in der Zwischenablage "vergessen" und somit schnell gelöscht oder
überschrieben werden kann. Eine andere Möglichkeit bieten Kommentare.
Üblicherweise werden Kommentare eingesetzt um zu beschreiben, was im
nachfolgenden Codeblock geschieht. Aber man kann diese während der Entwicklung
ebenso einsetzen, um Codezeilen vorübergehend vom Interpreter 'überlesen' zu
lassen.
Aus Performancegründen sollten diese Art Kommentare allerdings gelöscht werden,
sobald sie nicht mehr gebraucht werden. Spätestens aber wenn das Programm
'fertig' ist.
Man unterscheidet zwischen zwei verschiedenen Arten von Kommentaren:
dem Zeilenkommentar und dem Blockkommentar.

2.11.1 - Zeilenkommentare
Zeilenkommentare gehen - wie der Name schon sagt - nur über eine Zeile und
werden gekennzeichnet durch eine # zu Beginn der Zeile / des Kommentars.
Man kann Zeilenkommentare auf die komplette Zeile anwenden, oder eben nur auf
einen Teil der Zeile. Alles, was sich hinter der # befindet, wird also vom Interpreter
übersprungen und nicht ausgeführt.
# Dies ist ein Zeilenkommentar.
i = x % 5 # hier kann man kurz beschreiben, was passiert
Zeilenkommentare werden nicht nur genutzt, um Codezeilen vom Interpeter
"überlesen" zu lassen, sie werden hauptsächlich eingesetzt, um Codefragmente,
auch Routinen genannt, zu beschreiben. Dies dient dazu, den Code lesbarer zu
machen. Nicht nur für andere, auch für einen selbst. Nach einiger Zeit weiß man
sonst oft nicht mehr, was im entsprechenden Teil geschieht.

2.11.2 - Blockkommentare / Docstrings
Blockkommentare können über mehrere Zeilen gehen. Dazu müssen zu Beginn und
am Ende des Blockkommentars drei Anführungszeichen in Folge stehen.

""" Dies ist ein Blockkommentar.
Dieser darf auch mehrzeilig sein und wird auch häufig
eingesetzt, um Module, bzw. Funktionen zu beschreiben.
"""
Docstrings - oder auch Documentation-Strings werden meist für die Dokumentation
genutzt. Hier wird möglichst prägnant beschrieben, was in der Funktion geschieht.
Diese Beschreibung sollte so ausfallen, dass andere, die den Code dazu nicht
kennen, diesen nachbauen können.
Es gibt zusätzliche Tools, die aus diesen Docstrings eine Dokumentation des
Projekts erstellen, die im besten Fall sogar von Menschen verstanden werden, die
nicht programmieren können. Außerdem kann man in der Python Shell via my
_function.__doc__ oder mit print(my_function.__doc__) diese Beschreibung anzeigen
lassen
Damit aus den Docstrings eine Dokumentation erstellt werden kann, müssen sie ein
gewisses Format haben:
Zuerst kommt eine kurze Beschreibung des Moduls / der Funktion. Anschließend
sollten die zu übergebenden Parameter und ggf. Return-Value sowie deren Datentyp
beschrieben werden.
""" running_dot(color = 0x00FFFF, time_on = 50)
Neopixel-Animation, every single LED will light up in the
color specified by color for a time, specified in
time_on. after time has passed, LED is turned off and
next LED will light up.
color: color (hex) of the LEDs
time_on: time (ms) the LED will light up
""""

2.12 - Animationen Teil 3: white level
Wollen wir nun erreichen, dass nicht nur eine, sondern 3 aneinandergereihte LEDs
leuchten, diese aber mit jeder LED an Farbintensität abnimmt, können wir die vierte
Farbe der SK2812 LEDs nutzen: Weiß! Diesen Farbwert steuert man in strip.set() über
den Parameter 'white'. Dieser kann Werte zwischen 0 und 255 annehmen.
Die zweite LED, die also etwas weniger farbintensiv sein soll, können wir ganz
einfach in der Schleife mit pos=i-1 ansprechen.
strip.set(i-1, color, white=100)
... würde also die LED ansprechen, die sich direkt hinter der aktuell leuchtenden
befindet.
strip.set(i-2, color, white = 150)
... würde dann die LED ansprechen, die sich zwei Positionen dahinter befindet!
Aber: im Fall i=1 wäre i-2 = -1. Ein negativer Index kann aber zu einer Fehlermeldung
führen (Index Error - List Index out of range).
Daher müssen wir garantieren, dass der Wert für pos in strip.set nie kleiner als 1 und
auch nie größer als num_pixels ist.
Um zu garantieren, dass der Wert für i-1, i-2 nie kleiner als 0 und nie größer oder
gleich als num_pixels ist, können wir den Modulo-Operator, also Rest-Division
einsetzen.
pos1 = (i - 1) % num_pixels
pos2 = (i - 2) % num_pixels
Da bei der Modulodivision - genau wie bei der normalen Division - die Regel "Punkt
vor Strich" gilt, muss der Ausdruck (i - 1) zwingend in Klammern. Somit haben wir
garantiert, dass pos1 immer kleiner bleibt als num_pixels, da zum Beispiel 6 % 5
=1 (1 Rest 1).
Aber 5 % 5 = 1 Rest 0. Wenn wir Pixel 0 in cyan leuchten lassen, im nächsten
Schritt aber Pixel 1, dann würde jeweils dieselbe LED leuchten. Dafür würde aber
die letzte LED (an Stelle num_pixels) ausbleiben.
Um den "Fehler" in der MicroPython Implementierung wieder aufzufangen, muss bei
strip.set für pos wieder i+1 (, pos1+1, pos2+1, ..) übergeben werden:

strip.set(i+1, color, white=white_level, update = False)
strip.set(pos1+1, color, white=white_level, update = False)
strip.set(pos2+1, color, white=white_level, update = False)

3 - Steuerung der Animationen mit dem
Touch Sensor
Nun haben wir einige Animationen gebastelt, zwischen denen wir im Idealfall auch
wechseln wollen.
Natürlich könnten wir jetzt sagen, wir lassen diese einfach nacheinander abspielen,
sodass zum Beispiel Animation Nr. 1 für einige Zeit läuft, anschließend Animation Nr.
2, usw.
Besser wäre es doch, wenn wir selbst bestimmen könnten, wann die nächste
Animation starten soll.

3.1 - Der Touch Sensor
Unser ESP32 hat praktischerweise einige Sensoren an Board, wie zum Beispiel den
Touch Sensor (oder Berührungssensor).
Genauer gesagt haben wir insgesamt 10 Touch Sensoren. Diese befinden sich an
den Pins 0, 2, 4, 13, 12, 14, 15, 27, 33 und 32.
Beim Touch-Sensor handelt es sich um einen Kapazitiven Sensor.
Die Funktion dieses Sensors beruht auf der Änderung des elektrischen Feldes in der
Umgebung vor seiner Sensorelektrode (aktive Zone). Diese aktive Zone ist bei dem
verbauten Sensor allerdings sehr klein.
Im Prinzip handelt es sich hierbei um eine Art Kondensator. Da sich die Kapazität
eines Kondensators mit dem Abstand seiner Elektroden verändert, kann diese
messbare Größe zur Erkennung von Berührung eingesetzt werden.
Wir wollen uns momentan nur auf den Touch Sensor konzentrieren und lassen den
Teil mit den Animationen vorerst aus.
Alle Arten von Pins am ESP werden über das machine-Modul eingebunden.
Wollen wir also den Touch Sensor an Pin 27 nutzen, schreiben wir:

import machine
# touch related values
touchPin = 27
touch = machine.TouchPad(machine.Pin(touchPin))
Somit haben wir eine Instanz des Touchpads in der Variable touch gespeichert.
Wenn wir nun den Wert des Touch Pins auslesen wollen, schreiben wir:
touch.read()
Da es hin und wieder beim Auslesen zu Fehlern (ValueError) kommen kann und diese
dazu führen, dass die Ausführung des kompletten Programms anhält (was nur durch
Neustart behoben werden kann), müssen wir diesen Fehler abfangen.
Dieser Fehler tauchte bei meinen Tests anfangs überhaupt nicht auf, später dafür
immer wieder. Falls dieser Fehler zu häufig auftreten sollte, empfiehlt es sich, einen
anderen Pin als Touch-Sensor zu nutzen, weshalb ich die Pin-Nummer auch in eine
separate Variable gespeichert habe. Dies wird, sobald das Programm eine gewisse
Größe erreicht hat, zur Übersicht und besseren Wartbarkeit beitragen.
Um den Fehler abzufangen müssen wir dem Programm sagen, dass es versuchen
soll, diesen Pin auszulesen. Falls dies nicht möglich sein sollte, kann (muss aber
nicht) eine Ausgabe im Terminal geschehen.
Dies regelt man mit einer Aushnahmebehandlung (exception handling).
Der Code, der das Risiko für eine Ausnahme beherbergt, wird in ein try-Block
eingebettet. Abgefangen werden diese Ausnahmen im except-Block.
Wie bereits erwähnt wird die Ausführung des Programms linear vorgenommen.
Sprich es wird Zeile für Zeile ausgeführt. Wenn eine Funktion (auch aus anderen
Modulen) aufgerufen wird, geht die Ausführung des Programms - wieder Zeile für
Zeile - in der aufgerufenen Funktion weiter, bis diese komplett ausgeführt wurde.
Anschließend geht die Ausführung weiter in der Zeile nach der aufgerufenen
Funktion.
Da wir den Wert des Sensors mehr als nur einmal auslesen wollen um die Werte zu
vergleichen, müssen wir den Codeblock wieder in eine Endlosschleife packen. Es
reicht, den Wert 5-mal pro Sekunde auszulesen, also nehmen wir einen sleep-Wert
von 200 ms.

import machine, utime
# touch related values
touch_pin = 27
touch = machine.TouchPad(machine.Pin(touch_pin))
while True:
# try to read touch pin
try:
touch_val = touch.read()
except ValueError:
print("ValueError while reading touch_pin")
print(touch_val)
utime.sleep_ms(200)
Bevor wir aber den Touch Sensor testen können, müssen wir noch ein Kabel an den
Touch Pin (IO27) stecken. Nehmt dazu am besten ein JumperWire (male - female),
also eines, das man mit der einen Seite über den Pin stülpen kann und bei dem dann
auf der anderen Seite eine Spitze herausragt.
Main.py Speichern, Datei auf den ESP kopieren, Repl starten und Controller
neustarten. Schon sehen wir im Terminal, welche Werte der Sensor hat. Ohne
Berührung bewegt sich der Wert zwischen 400 und 500, mit Berührung grob
zwischen 30 und 150. Diese Werte können allerdings je nach
Umgebungsverhältnissen variieren
Setzen wir also den Schwellwert, ab dem der Sensor als "berührt" gelten soll, auf 180.
Falls es im weiteren Verlauf zu Schwierigkeiten kommen sollte, wenn zum Beispiel
eine Berührung registriert wurde obwohl keine stattgefunden hat, oder keine Reaktion
erfolgen sollte obwohl der Sensor berührt wurde, sollte man diesen Schritt
wiederholen.

3.1.1 - Animationen mit dem Touch Sensor starten
Um Animationen mit dem Touch Sensor zu starten, müssen wir also jedes Mal, wenn
der Schwellenwert unterschritten wurde, die nächste Animation starten.
Um einen Codeblock nur auszuführen, wenn eine bestimmte Bedingung zutritt, nutzt
man bedingte Anweisungen, auch Verzweigungen genannt.
So können wir bestimmen:
Wenn der Sensorwert kleiner ist als Schwellwert:
starte die nächste Animation.

3.2 - Bedingte Anweisungen / Verzweigungen
In der Programmierung versteht man unter einer bedingten Anweisung (oder
Verzweigung) Codeteile, die nur unter bestimmten Bedingungen ausgeführt werden.
Liegt diese Bedingung nicht vor, werden diese nicht ausgeführt. Alternativ kann (aber
muss nicht) stattdessen ein anderer Codeteil ausgeführt werden.
Anders ausgedrückt: Eine Verzweigung legt fest, welcher von zwei (oder auch mehr)
Programmteilen (Alternativen) in Abhängigkeit von einer (oder mehreren)
Bedingungen ausgeführt wird.
So könnte man ganz einfach sagen:
Wenn dies: mache das.
Dazu könnte man noch definieren:
Wenn dies nicht: mache was anderes.
Oder wenn man mehrere Bedingungen in Frage kommen könnten:
Wenn dies: mache das
Wenn dies nicht sondern das: mache jenes
wenn dies und das nicht: mache was anderes.
Hierzu nutzt man die if-Anweisung.
Nehmen wir an, wir haben den derzeitigen Wochentag in der Variable "weekday"
gespeichert. Wollen wir zum Beispiel erreichen, dass nur am Montag eine Anzeige im
Terminal erscheint, die einen an diesen (meist ungeliebten) Tag erinnert schreiben
wir:
if weekday == "monday":
print("today is monday. ")
der Operator "==" ist ein sogenannte Gleichheitsoperator. Dieser prüft, ob die beiden
Werte (davor und danach) gleich sind. Ist diese Bedingung erfüllt, wird True zurück
geliefert, die Bedingung wird somit wahr und der nachfolgende Block darf ausgeführt
werden.

Wollen wir, dass die Ausgabe nur erfolgt, wenn der Wochentag nicht der Montag ist,
so können wir auch das Gegenteilige mit einem Gleichheitsoperator prüfen. Also ob
eine Bedingung nicht stimmt. Hierzu nutzt man "!=" (=ungleich):
if weekday != "monday":
print("today's not monday")

Falls wir nur den Samstag oder Sonntag als Tag ausgeben, die restlichen Tage unter
der Woche aber nur, dass kein Wochenende ist:
if weekday == "saturday":
print("today is saturday")
elif weekday == "sunday":
print("today is sunday")
else:
print("no weekend today")
elif (Abkürzung von else if) wird nur ausgeführt, wenn die erste Bedingung (if) nicht
zutrifft.
Else wird nur ausgeführt, wenn sowohl if als auch elif nicht zutreffen.
Man kann beliebig oft verzweigen.

In unseren Code eingefügt sieht das nun so aus:
touch_threshold = 180
while True:
# read touch pin
try:
touch_val = touch.read()
expect ValueError:
# if ValueError occures print on terminal and blink
3 times in red
print("TouchPad Error")
animations.blink(color=0xff0000, count=3,
time_on=50, time_off=50)
# check if touch was registered
if touch_val < touch_threshold:
# print output and start function handleAnimations()
without argument to start next animation
animations.next()
utime.sleep_ms(200)
Jetzt haben wir aber mit animations.next() eine Funktion namens next erfunden, die
es in animations.py aber noch gar nicht gibt! Diese müssen wir noch in die Datei
animations.py einbauen.
Wie der Name schon sagt, soll diese Funktion einfach die nächste Animation
aufrufen.
Am einfachsten erreicht man dies, wenn man alle Funktionen in eine Liste oder in
einen Dictionary - schreibt, bei der man ganz einfach sagen kann: starte die Nächste!
Für den jetzigen Verwendungszweck würde eine Liste ausreichen. Aber da wir das
ganze später noch ausweiten wollen, werden wir hier einen Dictionary verwenden.
Spätestens wenn wir die Webseite erstellen, um bestimmte Animationen
auszuwählen, werden wir nicht um einen Dictionary kommen.
Zur Erinnerung:
Ein Dictionary zeichnet sich dadurch aus, dass zu jedem Wert ein Schlüsselwort
gehört, über den man diesen Wert auch abrufen kann.
In der Datei animations.py erstellen wir also zuerst einen Dictionary, mit dem wir
arbeiten können. In diesem werden alle vorhandenen Animationen, zusammen mit
einem Schlüsselwort - also den Namen der Animation als String - gespeichert.

Wichtig ist, dass diese Variable erst nach den Funktionen, die eingesetzt werden
sollen, deklariert wird. Beim Import der File animations.py werden - von oben nach
unten - alle Variablen und Funktionen angelegt. Würden wir den Dictionary zu Beginn
der Datei anlegen, würde der Interpreter die Funktionen noch gar nicht kennen und
somit einen Fehler melden. Daher werden die Variable und die zugehörige next()Funktion am Ende positioniert.
# saves all animations in a dictionary
animation_dict = ["blink" : blink, "running_dot" :
running_dot, "crossing_dot" : crossing_dots,
"rainbowCycle" : rainbow_cycle ]
Wie man sehen kann, werden hier nur die Bezeichner der Funktionen gespeichert.
Würden wir "blink()" anstatt "blink" schreiben, würde das zur sofortigen Ausführung
der Funktion führen, was wir aber nicht wollen.
Anschließend überlegen wir uns, was genau in der Funktion next() geschehen soll.
Innerhalb dieser Funktion möchten wir, dass, wenn bisher keine Funktion aufgerufen
wurde, die erste in der Liste gestartet wird. Falls aber im Vorfeld bereits eine
gestartet wurde, soll die jeweils nächste gestartet werden.
Eine einfache, aber komplizierte Lösung wäre, dass wir zum Beispiel sagen, dass
blink() die Animation Nr 0 ist, running_dot() Animation Nr. 1 (...), während ein Zähler
die Touch-Eingaben hochzählt.
Einfacher geht es mit einem Iterator

3.3 - Iterator
Ein Iterator ist einfach ein Zeiger, der auf das Element einer Liste (oder ähnlichem)
zeigt, das gerade an der Reihe ist. Er funktioniert ähnlich wie eine for-Schleife.
for i in range(3):
print(i)
könnte man auch folgendermaßen schreiben:
numbers = [0,1,2]
num_iterator = iter(numbers)
while True:
try:
num = num_iterator.__next__()
except StopIteration:
break
print(num)
Im ersten Moment scheint dies zwar komplizierter, aber oftmals - wie in unserem Fall
- definitiv die bessere Wahl.
Zu beachten ist hier auch der try / except - Bereich. Würden wir diesen auslassen,
würde es zu einer Fehlermeldung (StopIteration) und folglich zu einer Unterbrechung
des Programms kommen, sobald wir das Ende des Dictionarys erreicht, bzw.
überschritten haben.

Wir möchten aber nicht, dass die Iteration abbricht, nachdem wir die letzte Animation
erreicht haben. Wir wollen, dass, wenn das letzte Element erreicht wurde, die Iteration
wieder von vorn beginnt. Also behelfen wir uns eines kleinen Tricks:

Wir legen einfach eine neue Iteration an!
Dies führt uns aber zu einem weiteren Thema, das hier behandelt werden muss:

3.4 - Lokale und globale Variablen in Python
Jede Variable, die man in Python in einer Funktion definiert, ist automatisch eine
lokale Variable. Sprich sie existiert nur innerhalb dieser Funktion.
Man kann zwar innerhalb der Funktion auf eine Variable, die außerhalb der Funktion
definiert wurde, zugreifen, diese auch verändern. Jedoch hat diese Veränderung
keinen Einfluss auf die Variable außerhalb.
Sprich wenn eine Variable x außerhalb der Funktion den Wert 10 hat, wir diesen
innerhalb der Funktion durch 2 teilen, hat die Variable außerhalb der Funktion immer
noch den Wert 10, während die Variable x in der Funktion den Wert 5 hat.
Weitere Infos sowie einige Beispiele sind unter anderem im Python-Forum zu finden:
https://www.python-kurs.eu/python3_global_lokal.php

Die Variable animation_iterator wird außerhalb der Funktion next() deklariert
(=angelegt). Würden wir sie erst beim Aufruf der Funktion deklarieren, würde das
bedeuten, dass, bei jedem Aufruf die Iteration bei 0 beginnen würde.
Da wir sie aber - um nach Ende der Iteration wieder bei 0 zu beginnen - im exceptBlock neu anlegen und somit verändern müssen, würde dies zu einer Fehlermeldung
führen (Variable referenziert bevor sie deklariert wurde), da wir die Variable zuerst
verwenden und anschließend verändern. Gerne darf dieser Effekt ausprobiert
werden!
Daher müssen wir in der Funktion angeben, dass es sich hier um eine globale
Variable handelt.
Anders verhält es sich bei animation_dict. Diese wird in der Funktion lediglich
aufgerufen, nicht aber verändert. Daher muss animation_dict nicht global sein.

# saves all animations in a dictionary
animation_dict = ["blink" : blink, "running_dot" :
running_dot, "crossing_dots" : crossing_dots, "rainbowCycle" :
rainbow_cycle ] animation_iterator = iter(animation_dict)
def next():
global animation_iterator
try:
running_function = animation_iterator.__next__()
running_function()
except StopIteration:
animation_iterator = iter(animation_dict)
next()




running_function = animation_iterator.__next__() bewirkt, dass die nächste
Funktion in die Variable running_function gespeichert wird.
Mit running_function() wird diese dann ausgeführt.

Damit nun aber bei Erreichen des except-Blocks aus Sicht des Nutzers nicht einfach
'nichts' passiert, können wir - aus der Funktion heraus - selbe noch einmal aufrufen.
Wenn eine Funktion sich selbst aufruft nennt man diese eine rekursive Funktion. Da
diese im Projekt nur eine untergeordnete Rolle spielt, verweise ich auf Quellen im
Internet, um weitere Informationen zu erhalten:
https://www.python-kurs.eu/rekursive_funktionen.php
http://python4kids.net/how2think/kap04.htm
Fertig für den Test!
Also, Datei speichern, zum Terminal wechseln. Datei auf den ESP kopieren, Repl
starten, ESP neu starten.
Nun sollte es möglich sein, die erste Animation mit dem Touch Sensor zu starten.
Bei erneuter Berührung sollte nun eigentlich die laufende Animation stoppen und eine
neue starten.

Allerdings wird dies so nicht funktionieren!
Der Sensor reagiert nicht mehr auf Eingaben!
Dader Code linear abgearbeitet wird, wird die Ausführung von main.py unterbrochen
sobald die Animation gestartet wurde. Die Animation läuft ebenso in einer
Endlosschleife. Das bedeutet, es ist momentan nicht möglich, den Sensor
auszulesen und diese Animation von außen abzubrechen.
Natürlich könnten wir jetzt einfach sagen, wir begrenzen die Ausführung der
Animationen, sodass die Schleife nur noch 20 Mal ausgeführt werden kann. Aber
was ist, wenn wir die Animation früher wechseln möchten?
Um mehrere Prozesse quasi parallel laufen lassen zu können, behilft man sich mit
dem Prinzip des Multithreading.

3.5 - Multithreading
Multithreading wird wahrscheinlich den wenigsten ein Begriff sein. Dafür dürfte
Multitasking bekannt sein!
Jeder PC beherrscht Multitasking. So werden mehrere Programme quasi gleichzeitig
ausgeführt.
Quasi gleichzeitig bedeutet, sie laufen nicht wirklich gleichzeitig. Es bedeutet
lediglich, dass das Betriebssystem dafür sorgt, dass jedem Programm eine gewisse
CPU-Zeit zugesprochen wird. Sprich ein Zeitfenster, indem es die CPU (also den
Prozessor) beanspruchen darf. In Wirklichkeit laufen diese Prozesse gestückelt
nacheinander - aber in solch engen Zeitfenstern, dass es der User nicht wahrnimmt.
Multithreading verhält sich ähnlich, nur dass hier nicht zwei Programme gemeint
sind, die parallel laufen, sondern ein Programm in verschiedene Threads, also
verschiedene Aufgabenbereiche gegliedert wird, die parallel abzuarbeiten sind.
Bisher läuft unser Programm in einem Thread, dem main-Thread. Weitere Threads
können wir in unserem Code hinzufügen.
Hierfür nutzt man das Micropython Modul _thread.
Im Python-Forum wird nochmal das Verhalten vom Threads erklärt:
https://www.python-kurs.eu/threads.php
Allerdings können wir diese Beispiele nicht anwenden, da Micropython lediglich eine
Abgespeckte Version von Python ist.

Die Modul-Beschreibung zu _thread inklusive Beispielen findet man hier:
https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo/wiki/thread
Um eine Funktion in einem Thread zu starten, nutzt man
thread = _thread.start_new_thread(th_name, th_func,
args [, kwargs])






th_name ist der Name des Threads, der als String (also in doppelten
Anführungszeichen) anzugeben ist.
th_func ist die Funktion, die gestartet werden soll. Hier soll man nur den
Bezeichner angeben, ohne Klammer und Argumente
mit args werden dann, falls vorhanden, die Argumente als Tupel übergeben.
Falls keine Argumente übergeben werden, muss man eine leere Klammer
einfügen. Falls nur ein Argument übergeben wird, muss vor der schließenden
Klammer noch ein Komma stehen. Dies symbolisiert ein Tupel mit nur einem
Wert. Zum Beispiel (arg1, )
kwargs sind optional. Hier werden die optionalen Parameter mit ihren
Schlüsselworten übergeben. Diese müssen als Dictionary eingetragen werden.
Bsp: {"arg1": value1, "arg2": value2}.

Die Funktion gibt beim Aufruf die ID des Threads zurück, die dann in einer Variablen
(hier: thread) gespeichert werden sollte.
Diese ID wird benötigt, um die Ausführung des Threads auszusetzen oder ihn wieder
zu stoppen.
mit _thread.suspend(thread_id) und _thread.resume(thread_id) wird die Ausführung
des Threads unterbrochen und anschließend wieder fortgesetzt.
Dies funktioniert allerdings nur, wenn in der auszuführenden Funktion die
Unterbrechung mit _thread.allowsuspend(True) erlaubt wurde.
Möchte man den Thread stoppen, kann man _thread.stop(thread_id) nutzen.
Dadurch wird dem Thread allerdings nur eine Benachrichtigung geschickt! Damit
diese auch verarbeitet werden kann, muss in dem Thread, der abgebrochen werden
soll, die Funktion notification = _thread.getnotification() ausgeführt werden. Sobald
in notification derselbe Wert steht, der auch _thread.EXIT hat (EXIT ist keine normale
Variable sondern eine Konstante. Wenn man hier einen Namen anstatt eines Werts
nutzt trägt das zur besseren Lesbarkeit bei) Alternativ kann man auch
_thread.wait(timeout) nutzen. Dies ist sehr praktisch, da man diese Funktion anstelle
von utime.sleep_ms(timeout) einsetzen kann. Damit wird erreicht, dass während des

Timeouts trotzdem Berichtigungen erhalten werden können und auch
währenddessen ein Abbruch möglich ist.
Durch das Schlüsselwort return wird die derzeitig laufende Schleife (egal ob endlich
oder endlos) wieder verlassen wird.

3.5.1- Implementierung von _thread
Wir haben nun schon einige Animationen geschrieben. Bevor wir nun in jeder
einzelnen prüfen, ob eine Benachrichtigung erhalten wurde, packen wir das ganze
lieber in eine Funktion.
Es gehört zu einem guten Programmierstil, dass kein gleicher Codeblock mehrmals
vorkommt. In dieser Funktion wollen wir den timeout laufen lassen (der
utime.sleep_ms ersetzen soll). Also muss der Funktion mitgeteilt werden, wie lange
dieser timeout sein soll.
Dann wollen wir prüfen, ob eine Benachrichtigung vorliegt. Wenn die
Benachrichtigung _thread.EXIT ist, wollen wir zurückgeben, dass die
Abbruchbedingung gegeben ist. Also brauchen wir ein return-Statement.
Dies können wir so erreichen:
def waitForExitNotification(timeout):
notification = _thread.wait(timeout)
if notification == _thread.EXIT:
return True
else:
return False
Um diese Funktion in den Animationen aufzurufen und den Return-Wert zu
verarbeiten können wir utime_sleep_ms(timeout) durch folgendes ersetzen:
exit = waitForExitNotification(timeout)
if exit:
return
Dies fügen wir jetzt anstelle von utime.sleep_ms in jede Animation ein, die in einer
Endlosschleife läuft. Nicht vergessen: Den Wert von timeout angeben!
Jetzt haben wir unsere Funktionen so angepasst, dass sie mit den
Benachrichtigungen umgehen können. Nun müssen wir noch dafür sorgen, dass der
Thread gestartet und gestoppt werden kann.

Um nicht für jede einzelne Animation _thread.start anlegen zu müssen, können wir
sagen, dass animations.next() einfach im Thread gestartet werden soll. der Aufruf
der Animation geschieht dann innerhalb von next().
Also ersetzen wir in main.py animations.next() durch
animation_thread = _thread.start_new_thread("animation",
animations.next, () )
Jetzt haben wir den Start der Animation geregelt, aber noch nicht den Abbruch!
Wenn wir aber bereits eine Animation am Laufen haben und wir diese zuerst
abbrechen müssen, müssen wir eine Eigenschaft der Variablen animation_thread
prüfen bevor sie deklariert wurde!
Variablendeklarationen sollten, wenn es möglich ist, immer zu Beginn einer Funktion
oder eines Moduls geschehen - es sei denn, es werden dazu Angaben benötigt, die
erst später im Code vorkommen (wie im Fall des animation_dict).
Also werden wir dies in main.py auch zu Beginn - am besten nach den
Variablen für touch - einsetzen und den Wert 100 zuweisen.
_thread.stop(animation_thread) wird eine Zeile über der Zeile, in der der Thread
gestartet wird, eingesetzt.

Hier nochmal die komplette main.py:
import machine, utime, _thread
import animations
touch_pin = 27
touch = machine.TouchPad(machine.Pin(touch_pin))
touch_threshold = 180
animation_thread = 100
while True:
try:
touch_val = touch.read()
except ValueError:
print("ValueError while reading touch_pin")
if touch_val < touch_threshold:
print("touched!!")
_thread.notify(animation_thread, _thread.EXIT)
utime.sleep_ms(50)
animation_thread =
_thread.start_new_thread ("animation",
animations.next, () )
utime.sleep_ms(200)

3.6 - Helligkeit der LEDs steuern
Bestimmt ist bereits aufgefallen, wie hell die LEDs leuchten können. Diese Helligkeit
kann man aber verändern!
Relativ einfach kann man mit Neopixel.brightness(brightness_value)
bestimmen, wie hell die LEDs leuchten sollen. brightness_value kann hier einen Wert
zwischen 0 und 255 annehmen.
Gerne kann dies wieder in Repl ausprobiert werden! Allerdings empfehle ich, dies bei
unterschiedlichen Lichtverhältnissen zu testen, um ein Gefühl dafür zu bekommen,
wann welcher Wert passt.
>>> import machine, animations
>>> animations.running_dot()
>>> animations.strip.brightness(1)

3.6.1 - Der Photowiderstand
Selbstverständlich kann man diese Werte manuell definieren. Aber viel besser wäre
es doch, wenn dies automatisch, abhängig von der Umgebungshelligkeit, geschehen
würde - so wie es auch bei Smartphones passiert!

Hierzu nutzt man einen Photowiderstand.
Ein Photowiderstand ist ein Halbleiter, dessen Widerstand abhängig von der
Umgebungshelligkeit ist. Er wird auch LDR (=Light Dependent Resistor) genannt.
Er wird oft als Beleuchtungsstärkemesser, Flammenwächter, Dämmerungsschalter
und als Sensor in Lichtschranken verwendet.
Um den Photowiderstand am ESP einzusetzen muss man wissen, dass der ESP
grundsätzlich eigentlich nur digitale Ein- und Ausgänge hat. In der Regel können
diese nur Binärcode ausgeben und entgegennehmen.
3.6.1.1 - Analog Digital Converter - das ADC Modul
Allerdings besitzt der ESP auch einen Analog-Digital-Wandler, auch ADC (=Analog
Digital Converter) genannt.
Den ADC findet man - wie alles, was mit Pins zu tun hat - im machine Modul:
https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo/wiki/adc
Die Modulbeschreibung sieht relativ komplex aus für Menschen, die mit
Elektrotechnik nicht viel zu tun haben.
Aber wir setzen hier nur die nötigsten Bausteine ein, um die Umgebungshelligkeit zu
messen.
Zu beachten ist allerdings, dass es zwei Arten von ADC gibt. ADC1 und ADC2.
Jeweils auf verschiedenen Pins. ADC2 kann man allerdings nur nutzen, wenn WLAN
nicht aktiv ist. Da wir die WLAN Funktion später noch brauchen werden, nutzen wir
ADC1.
Diesen kann man auf den Pins 32 - 38 nutzen.



Eine Instanz des ADC bilden wir mit ldr = machine.LDR(pin[, unit]), wobei unit
standardgemäß 1 (für ADC1) ist. Daher muss dies nicht angegeben werden.



ldr_val = lrd.read() liest die Spannung (in mV), die am Sensor anliegt und
speichert sie in ldr_val.

Aus der Modulbeschreibung (machine.ADC.vref) geht hervor, dass die Spannung
einen Wert zwischen 0 und 1100 mV annehmen kann, wobei 0 als sehr dunkel und
1100 als sehr hell zu interpretieren ist.
Diesen Wert können wir also nicht einfach 1:1 nutzen, um die Helligkeit der LED zu
setzen, da diese nur Werte zwischen 0 und 255 akzeptiert. Für den Anfang reicht es
daher, den Wert des Sensors auf den Helligkeitswert zu "mappen".
Dies ist ganz einfache Mathematik:
brightness_val = ldr_val / 1100 * 255.
Würde der LDR nun einen Wert von 700 haben, würde als Ergebnis 162,272727273
herauskommen. Das Problem dabei: Neopixel.brightness kann nur mit ganzen
Zahlen, sogenannten Integer umgehen!
Dies ist aber leicht behoben. int(brightness_val) sorgt dafür, dass aus der
Gleitkommazahl 162,272727273 die Ganzzahl 162 wird.
Hierbei wird allerdings nicht gerundet! der Wert wird einfach nach dem Komma
abgetrennt, sodass wir eine ganze Zahl erhalten. Da wir keine sensiblen
Berechnungen durchführen ist dies vernachlässigbar. Um Gleitkommazahlen zu
runden, gibt es Funktionen im Modul math.
brightness_val können wir nun einfach einer Funktion in animations.py übergeben,
die dann die Helligkeit des Strips setzt.
Die Funktion, um die Helligkeit in animations.py zu setzen sieht ganz einfach aus:
def set_brightness(brightness_val):
strip.brightness(brightness_val)
Aufgerufen wird sie in main.py - am besten in der Endlos-Schleife:
while True:
ldr_val = ldr.read()
brightness_val = int(ldr_val / 1100 * 255)
Diese vier Zeilen lassen sich auch auf zwei komprimieren!
while True:

animation.set_brightness(int(ldr.read() / 1100 * 255)t)
3.6.1.2 - Photowiderstand mit ESP verbinden
Um den Photowiderstand ordnungsgemäß auszulesen, müssen wir einen
sogenannten Spannungsteiler bauen. Hierzu benötigen wir neben dem
Photowiderstand und einer Leitung, um die Werte an den ESP zu übertragen eine
Spannungsquelle mit 3.3V und einen passenden Widerstand (1 kΩ). Eine Seite des ). Eine Seite des
LDR verbinden wir also mit dem 3.3V Ausgang am ESP, die andere Seite wird mit
dem Widerstand verbunden. Diesen wiederum verbinden wir mit GND am ESP um
den Stromkreis zu schließen. Um die Werte auszulesen, müssen wir nun einen der
ADC Pins (hier Pin 36) zwischen LDR und Widerstand mit dem Stromkreis verbinden.

Mit einem kleinen Breadboard kann man die Schaltung ganz einfach umsetzen:

3.6.1.3 - Das Breadboard

Zum besseren Verständnis: das linke Bild beinhaltet die LDR-Schaltung ohne
angeschlossenen LED Strip, Bild 2 mit angeschlossenem LED-Strip.
Um die Schaltung besser zu verstehen, muss man wissen, wie ein Breadboard
aufgebaut ist:
Ein Breadboard besteht aus sehr vielen kleinen Steckplätzen, die auf eine bestimmte
Weise miteinander verbunden sind:
Auf der linken und rechten Seite befinden sich die Steckplätze, die am besten für VCC
oder GND eingesetzt werden. Diese Steckplätze sind jeweils senkreicht miteinander
verbunden. So kann ich, wenn ich für zwei oder mehr Elemente GND brauche, den
GND vom ESP einfach mit dieser Reihe verbinden. Genauso verfahre ich mit GND der
die LED sowie mit GND des LDR. So kann ich einen GND-Anschluss am ESP für
mehrere Bauteile verwenden.
Im mittleren Teil sind jeweils fünf Steckplätze auf der rechten und auf der linken
Seite waagerecht miteinander verbunden. So kann ich - wie hier - eine Seite des LDR,
eine Seite des Widerstands und ein Kabel zum Auslesen der Werte in eine Reihe
stecken, um sie miteinander zu verbinden.
3.6.1.4 - LDR testen
Nachdem der LDR ordnungsgemäß angeschlossen wurde und die Dateien auf den
ESP kopiert wurden, darf nun getestet werden.

Dazu kann man den LDR einfach mit der Hand bedecken, oder mit einer Leuchtquelle
hellerem Licht aussetzen.
Wenn alles richtig gemacht wurde, sollten die LEDs heller leuchten, je heller die
Umgebung ist.

4 – Steuerung der Animationen mit dem
WebServer
Ein Webserver ist ein System, das Webseiten bereitstellt. Um aber auf diese
Webseiten zugreifen zu können, müssen sich der Server und der Client, der die Seite
aufrufen möchte, im selben Netzwerk (oder im Internet) befinden.
Wir wollen den Webserver nutzen, um über die Webseite direkt unsere Animationen
auszuwählen.
Um dies zu ermöglichen, muss man zuerst dafür sorgen, dass man sich mit seinem
Rechner oder Smartphone mit dem ESP verbinden kann. Hierfür ist das networkModul zuständig.

4.1 - WLAN auf dem ESP
4.1.1 - Das network-Modul
Das network-Modul bietet neben der Möglichkeit, eine WLAN Verbindung
herzustellen unter anderem die Möglichkeit, einen FTP-Server (zur Bereitstellung von
Datei(Systemen)), einen Telnet-Server (um Kommandos über das Terminal zu
senden), einen MQTT-Client (zum Senden und Empfangen von Nachrichten Facebook Messenger nutzt zum Beispiel das MQTT-Protokoll) oder einen mDNSService (zur Namensauflösung - sprich um eine URL in eine IP-Adresse - und
andersrum - zu konvertieren). Wir beschränken uns aber vorerst auf die WLANConnectivity.
https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo/wiki/network
Um den Code übersichtlich zu halten, wird an dieser Stelle empfohlen, eine neue
Datei anzulegen. Ich nenne sie mal wifi.py. Selbstverständlich beginnen wir auch hier
damit, die erforderlichen Module einzubinden:
import network, utime
Man kann den ESP als Station (STA) nutzen, was bedeutet, dass der ESP sich
lediglich mit einem anderen AccessPoint (einem Router zum Beispiel) verbinden
kann, als AccesPoint (AP), wodurch sich andere Geräte (wie unser Computer oder
Mobiltelefon) mit dem ESP verbinden können, oder aber als Kombination aus
beidem, was bedeutet, er kann sich mit einem Router verbinden und gleichzeitig als
AccesPoint dienen.

4.1.1.1 - AP-Mode
Um also den ESP als WebServer nutzen zu können, müssen wir eine Instanz von
WLAN im AP-Mode nutzen:
ap = network.WLAN(network.AP_IF)
Dadurch wurde das WLAN-Interface allerdings noch nicht gestartet! Dies
geschieht erst durch
ap.active(True)
Zudem müssen wir noch eine SSID (Service Set Identifier = der Name des WLANNetzwerks) und ein zugehöriges Passwort definieren. Hierbei handelt es sich um eine
Konstante, die groß geschrieben werden sollten:

AP_SSID = "esp32"
AP_PASS = "HuchEinPw"
Diese Einstellungen muss man dem ESP noch mitteilen:
ap.config(essid=AP_SSID, password=AP_PASS)
Anschließend sollte man die Konfiguration (wie zum Beispiel die IP-Adresse, unter
der der ESP erreichbar ist) mit print(ap.ifconig()) im Terminal ausgeben. Dies geht
aber nur, sobald der AccessPoint gestartet wurde, was eventuell länger dauern
könnte wie die Zeit, die zwischen diesen beiden Befehlen vergeht.
WLAN.isconnected(False) wartet im AP-Mode nicht etwa darauf, bis sich ein Client
verbunden hat. Dieses "False" bewirkt, dass die Funktion nur True zurückgibt, wenn
der AP-Mode gestartet wurde.
timeout = 50
while not ap.isconnected(True):
utime.sleep_ms(100)
timeout -= 1
if timeout == 0:
break
print("\n======== AP started =======\n")
print(ap.ifconfig())
Bei der Zeichenfolge "\n" handelt es sich um eine sogenannte Escape-Sequenz.
Diese wird in Strings dazu genutzt, den "Cursor" um eine Zeile nach unten zu
schieben.
Weitere Infos hierzu:

https://de.wikipedia.org/wiki/Escape-Sequenz
Um diesen Code nach dem Start ausführen zu lassen, reicht es aus, die Datei - analog
zu animatons.py - in main.py mittels import einzubinden.
Nachdem nun beide Dateien gespeichert und auf dem ESP aktualisiert wurden kann
man die Funktionalität prüfen, indem man die verfügbaren WLAN-Netzwerke prüft.
Befindet sich hier ein Netzwerk namens "esp32" und kann man sich mit dem
angegebenen Passwort damit verbinden, ist dieser Schritt auch geschafft!

4.1.1.2 - STA-Mode
Sobald man länger mit dem Projekt arbeitet, kann es Umständlich werden, den ESP
lediglich im AP-Mode zu betreiben. Die Endgeräte registrieren, dass zwar WLAN
verbunden ist, jedoch keine Internetverbindung besteht. Das kann dazu führen, dass
sich die Geräte automatisch wieder mit dem heimischen (oder schulischen)
Netzwerk verbinden. Oder aber denkt nicht daran, dass man mit dem ESP verbunden
ist und wundert sich, warum man keine anderen Webseiten aufrufen kann. Beim
Debuggen (Fehler finden und entfernen) und Testen des Webservers kann dies sehr
zeitraubend sein.
Somit erstellen wir eine weitere Instanz, dieses Mal im STA-Mode, und aktivieren
diese:
sta = network.WLAN(network.STA_IF)
sta.active(True)
Auch hier müssen wir SSID und Passwort angeben - allerdings vom heimischen
Router:
STA_SSID = "DeineSSID"
STA_PASS = "DeinPw"
Um Fehlfunktionen zu vermeiden, die auftreten können, sobald man nicht in
Reichweite des Heimnetzes ist, sollte man vorher prüfen, ob eine SSID mit dem
gegebenen Namen vorhanden und nur dann versuchen, eine Verbindung
herzustellen. Auch hier sollte jemandem ESP etwas Zeit geben, bis er sich verbunden
hat:
networks = sta.scan()
for nets in networks:
if nets[0] == bytes(STA_SSID, 'utf-8'):
sta.connect(STA_SSID, STA_PASS)

timeout = 50
while not sta.isconnected():
utime.sleep_ms(100)
timeout -= 1
if timeout <= 0:
break
if sta.isconnected():
print("\n======== STA CONNECTED ========\n")
print(sta.ifconfig())
if not sta.isconnected():
print("\n======== STA NOT CONNECTED ========\n")
sta.active(False)
sta.scan() scannt alle verfügbaren WLAN-Netzwerke und speichert sie in
networks.
 networks beinhaltet dann eine Liste mit Tupel. Jeweils an erster Stelle [0]
dieser Tupel ist die SSID, allerdings in bytes, zu finden.
Da STA_SSID aber ein String ist, muss man diesen ebenso in bytes konvertieren, um
beide Angaben miteinander vergleichen zu können.


Durch diesen Vergleich wird gewährleistet, dass ein Verbindungsversuch nur
stattfindet, wenn die angegebene SSID auch Verfügbar ist.

4.1.1.3 - mDNS Service
Nachdem eine Netzwerkverbindung im jeweiligen Modus erfolgreich hergestellt
wurde, werden jeweils die IP Adressen ausgegeben, unter denen unsere Website
später erreichbar sein wird.
Leider können sich nur wenige Menschen solche Zahlenfolgen dauerhaft merken!
Aus diesem Grund gibt es DNS-Server (Domain Name System).
Ein DNS-Server wird auch kurz "Telefonbuch des Internets" genannt. Ähnlich wie man
in einem Telefonverzeichnis nach einem Namen sucht, um die Telefonnummer
heraus zu bekommen, schaut man im DNS nach einem Computernamen, um die
dazugehörige IP-Adresse zu bekommen. Die IP-Adresse wird benötigt, um eine
Verbindung zu einem Server aufbauen zu können, über den nur der Computername
bekannt ist.
mDNS erfüllt einen ähnlichen Zweck- nur im lokalen Netzwerk. Somit ist die Seite
unter [Domain-Name].local zu erreichen.

https://de.wikipedia.org/wiki/Zeroconf#Multicast_DNS
https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo/wiki/mdns
Folgendermaßen erreichen wir, dass unsere Website unter der Adresse
"rainbowwarrior.local" aufzurufen ist:
hostname = "RainbowWarrior"
try:
mdns = network.mDNS()
mdns.start(hostname, "MicroPython with mDNS")
mdns.addService('_http', '_tcp', 80, "MicroPython",
{"board":"ESP32", "service": "mPy Web server"})
print("webpage available at {}.local".format(hostname))
except:
print("mDNS not started")
Leider funktioniert dies nicht mit Android-Mobiltelefonen. Hier muss man weiterhin
die IP-Adresse im Browser angeben. Diese werden allerdings bei erfolgreichem
Aufbau der Verbindung(en) im Terminal ausgegeben.

4.1.2 - Das microWebSrv-Modul
Ein Webserver erfüllt die Funktion, Webseiten (im Internet) bereitzustellen.
Selbstverständlich können wir auf dem ESP nicht den vollen Funktionsumfang eines
richtigen Servers erwarten. Aber die Grundfunktionalität ist gewährleistet.
Das microWebSrv-Modul stellt HTML-Seiten bereit, die - entweder als String vorliegen
- oder als html-File in /flash/www auf dem Microcontroller gespeichert sind.
Eine Modulbeschreibung inklusive Beispiele ist hier zu finden:
https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo/wiki/
microWebSrv
Auch hier empfiehlt es sich, den Webserver-Code in eine eigene Datei zu packen.
Nennen wir sie webSrv.py.
Neben dem MicroWebSrv beinhaltet microWebSrv auch MicroWebSockets, was wir
allerdings nicht benötigen. Daher genügt es, den import auf MicroWebSrv zu
beschränken:
from microWebSrv import MicroWebSrv
Um eine Instanz des MicroWebSrv zu erstellen und diesen zu starten schreiben wir:

srv = MicroWebSrv(webPath = 'www')
srv.Start(threaded = True, stackSize= 8192)




webPath gibt an, in welchem Ordner die HTML-Dateien zu finden sind.
threaded = True ist zwingend notwendig, da wir parallel in main.py weiterhin
die Touch Sensor-Eingaben sowie auch die Anfragen vom WebServer
verarbeiten müssen.

Um die gespeicherten Seiten nun über die URL aufrufen zu können, müssen wir
Route-Handler definieren. So wird gewährleistet, dass wir unter
„rainbowwarrior.local“ unsere Startseite aufrufen können, bzw unter
rainbowwarrior.local/[irgend-ne-andere-seite] andere Seiten, die wir bereitstellen
wollen. Hier ein einfaches Beispiel für einen Route-Handler, der bei einem Zugriff
(bzw. einer Anfrage) auf rainbowwarrior.local die Seite index.html (im Ordner www)
zurückgibt:
@MicroWebSrv.route('/')
def _httpHandler(httpClient, httpResponse) :
httpResponse.WriteResponseFile(filepath =
'www/index.html', contentType= "text/html",
headers = None)
Die Route-Handler sollten bekannt sein bevor die WebServer-Instanz erstellt wird,
weshalb man diese bitte oberhalb davon in die Datei einträgt.
Eine hübsche Webseite habe ich bereits vorbereitet. Diese muss nun nach und nach
mit den bereits erstellten Animationen gefüllt werden.
Die Seite wurde mithilfe von Bootstrap und CSS aufgehübscht. Für eine fehlerfreie
Darstellung ist es also notwendig, dass zwei Dateien im Ordner '/ flash/www'
vorhanden sind:
https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/
bootstrap.min.css
https://github.com/crxcv/Leuchteding/blob/master/pyboard_code/www/
style.css
Falls ein einfacher Download nicht möglich sein sollte, können die Inhalte auch mit
copy + paste in Files mit den Namen "bootstrap.min.css" und "style.css" übertragen
werden.

4.2 - HTML
Diese werden auf der HTML-Seite (index.html) direkt im -Tag eingebunden:

RainbowWarriorSettings
 



Hier einige Basics zu HTML.
Kommunikation zwischen HTML-Seite und dem Server:
https://wiki.selfhtml.org/wiki/HTTP/Anfragemethoden
Formulare:
https://wiki.selfhtml.org/wiki/HTML/Formulare/form

4.2.1 - Aufbau einer HTML-Seite
HTML ist eine „Strukturierungssprache“, mit der dem Browser „gesagt“ wird, wie
die Inhalte strukturiert sind: welche Bereiche (Buchstaben, Wörter, Sätze) z.B.
Überschriften, was Absätze, was Aufzählungen, was Tabellen usw. sind.
HTML ist keine Programmiersprache – man beschreibt, welche logische
Struktur ein Inhalt hat (nicht mehr, nicht weniger).
Jeder einzelne Bereich wird mit einem Tag definiert. Der Beginn des Bereichs (am
Beispiel Absatz - (engl. paragraph)) wird mit einem öffnenden Tag 

, das Ende des Bereichs mit einem schließenden Tag

gekennzeichnet. Die verschiedenen Bereiche 1. DOCTYPE Im Bereich DOCTYPE wird dem Internet-Browser mitgeteilt, was er an Befehlen erwarten kann und an welchem Standard man sich bei der Erstellung der Seite gehalten hat. 2. HEAD Titel der Webseite Im Bereich HEAD stecken die Metainformationen über die Seite, also beispielsweise der Titel der Seite, der im Browserfensterkopf angezeigt wird sowie Links zur CSSFile. CSS (Cascading Style Sheet) ist für das Aussehen der der Webseite verantwortlich 3. BODY ... Im Bereich BODY steckt der eigentliche Inhalt der Seite und die HTML-TAGs. Mehr Informationen hierzu inklusive einem Tutorial findet man unter anderem hier: https://www.html-seminar.de/einsteiger.htm https://www.html-seminar.de/html-grundlagen.htm https://www.html-seminar.de/html-seitenaufbau.htm https://www.html-seminar.de/css-lernen.htm 4.2.2 - HTML - Formulare Formulare eignen sich hervorragend um Nutzereingaben in Form von Text (Bsp. Name, Nachrichten ...) oder einem Auswahlmenü an den Server zu senden. https://www.html-seminar.de/formulare.htm https://wiki.selfhtml.org/wiki/HTML/ Formulare 4.2.3 - HTTP - Kommunikation mit dem Server Bei HTTP handelt es sich um eine Art Sprache, in der ein Webserver und ein Browser miteinander kommunizieren. Diese Kommunikation erfolgt nach einem festgelegten Schema, das man auch "Protokoll" nennt. HTTP ist also ein Protokoll, daher auch der Name "HyperText Transport Protocol". Request und Response Beim Aufruf einer Seite sendet der Browser zuerst einen "Request" (Anfrage, Bsp.: ich möchte die Seite sehen), die vom Server mit einem "Response" (Antwort, Bsp: ich zeige Dir die Seite) beantwortet wird. GET und POST Jeder Request wird durch die Angabe der Methode eingeleitet. Methoden bestimmen die Aktion der Anforderung. Die aktuelle HTTP-Spezifikation sieht acht Methoden vor: OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE und CONNECT. Wir beschränken uns jetzt aber auf die Methoden GET und POST. Die mit Abstand wichtigste Methode ist die Methode GET. Hiermit wird - mittels einem Request - zum Beispiel ein Dokument (die aufzurufende HTML-Seite) angefordert. Als Antwort auf diesen Request erhält der Client (unser Browser) die HTML-Seite als Response Die POST-Methode übermittelt in erster Linie Formulareingaben an den Webserver. Werden durch den Request lediglich andere Daten als Antwort empfangen, so ist die GET-Methode die richtige Wahl. Werden durch den Request Daten auf dem Server verändert, ist die POST-Methode die richtige Wahl. Werden Daten für Logins, insbesondere Passwörter übermittelt, dann ist nur POST die einzig richtige Wahl. https://de.wikipedia.org/wiki/ Hypertext_Transfer_Protocol https://wiki.selfhtml.org/wiki/HTTP/Einsteiger-Tutorial https://wiki.selfhtml.org/wiki/HTTP/Anfragemethoden 4.2.4 - Die HTML-Seite RainbowWarriorSettings
variant-2">
variant-3">
variant-4">
variant-5">

RainbowWarrior

LED Pattern auswählen:

Das meiste des hier gezeigten HTML-Codes ist lediglich für die Darstellung der Objekte und die Animationen im Hintergrund zuständig. Für uns sind lediglich folgende Zeilen wichtig, in denen ein Dropdownmenü zur Auswahl der Animationen erstellt wird:

LED Pattern auswählen:

Eine HTML-Form erfüllt den Zweck, User-Eingaben entgegenzunehmen und an den Server zur Weiterverarbeitung zu senden. Dazu muss mit "method" angegeben werden, mit welcher Methode (hier: Post) gearbeitet wird und mit "action", welcher Route-Handler verwendet werden soll. Die Form beginnt mit dem öffnenden HTMLTag
: und endet mit dem schließenden Tag
: Direkt unter dem öffnenden form-Tag befindet sich das h3-Tag. Hier wird eine Überschrift angegeben. Dies ist allerdings optional. Die Formatierung von h3 wird in der Datei style.css festgelegt.

LED Pattern auswählen:

h3 steht für "Header 3" - also Überschrift Nr. 3. Wobei mit der Nummerierung die Rangfolge angegeben wird. h1 ist in der Regel sehr groß. Die Größe ändert sich mit der Rangfolge. Um die Daten an den Server zu senden, wird ein Button verwendet:
Welche Werte an den Server gesendet werden, werden durch die Optionen name und value definiert. Diese werden dann als Dictionary { name:value} an den Server übergeben So wird ein Dropdown-Menü erstellt. Im öffnenden select-Tag wird der name (light) des zu übergebenden Werts bestimmt. Innerhalb des select-Tags werden die options angegeben, also die Werte, die dann mittels dem Dropdown-Menüs ausgewählt werden können. Hier wird auch der zu übergebende value definiert. Nun müssen wir nur noch unsere Animationen einfügen. Für jede unserer Animationen müssen wir nun zwischen    value="running_dot" ist der Wert, der nach dem Absenden vom Server weiter verarbeitet wird. Ihn werden wir nutzen, um die richtige Animation zu starten. Hier ist es wichtig, dass der Eintrag in value derselbe ist, den wir im Dictionary animation_dict in animations.py angegeben haben! andernfalls werden wir die Animation nicht starten können. Running Dot - was vor dem schließenden -Tag steht ist lediglich der Text, der im Dropdown Menü angezeigt wird.. Die "Hieroglyphen" ä werden zur Darstellung von Umlauten benötigt, da nicht jeder Browser Umlaute in Form von 'ä', 'ö', 'ü' darstellen kann. Hier nutzt man stattdessen ä (für ä), ö (für ö) und ü (für ü). 4.3 - Eingaben der Webseite verarbeiten Nun wissen wir, dass für die erste Darstellung der Seite die GET-Methode, für das Absenden der Formulardaten die POST-Methode zum Einsatz kommen. 4.3.1 - Route-Handler des microWebSrv Moduls Das bedeutet wir brauchen zwei Route-Handler-Methoden in webSrv.py. Der Route-Handler für GET muss also vor der Definition folgende Zeile enthalten: @MicroWebSrv.route('/') Der Route-Handler für POST wird mit folgender Zeile eingeleitet: @MicroWebSrv.route('/', 'POST') für jeden Request brauchen wir eine eigene Funktion, die entweder nur die Seite als Antwort sendet, oder die Eingabedaten weiter verarbeitet bevor die Seite als Antwort gesendet wird. Leider können wir - obwohl beide Funktionen teilweise selben Inhalt haben, diese beiden nicht kombinieren. Für den GET-Request reicht es also aus, die Seite als Antwort zu senden: @MicroWebSrv.route('/') def _routeHandlerGet(httpClient, httpResponse): httpResponse.WriteResponseFile( filepath = 'www/index.html', contentType= "text/html", headers = None) Beim POST-Request muss der Server zusätzlich die eingehenden Daten abgerufen werden. Ein Blick in die Modulbeschreibung verrät uns, wie wir an die FormData herankommen können: @MicroWebSrv.route('/', 'POST') def _routeHandlerGet(httpClient, httpResponse): formData = httpClient.ReadRequestPostedFormData() httpResponse.WriteResponseFile( filepath = 'www/index.html', contentType= "text/html", headers = None) Nun müssen wir diese empfangene Daten an main.py senden. Wie bereits erwähnt, läuft der WebServer in einem eigenen Thread. So wie auch der MainThread ein eigener Thread ist. Auch unsere Animationen laufen in einem eigenen Thread. Wir könnten jetzt - wie bei animations.set_brightness auch sagen: wir schreiben eine Funktion namens get_values in webSrv.py, die wir von main.py aus aufrufen. Dies würde uns allerdings immense Probleme bereiten, wenn main.py auf die Variable zugreift, während sie zur gleichen Zeit von webSrv.py geändert wird (und dies kommt häufiger vor als man denkt!) Daher ist hier der Einsatz von Thread-Messages die sicherere Wahl. Sobald neue Daten vorliegen, kriegt main.py eine Benachrichtigung und holt sie dann erst ab: if "light" in formData: _thread.sendmsg(_thread.getReplID(), "light: {}".format(formData["light"]))     if "light" in formData prüft, ob die Zeichenfolge "light" in formData zu finden ist, um fehlerhafte Verarbeitung auszuschließen. _thread.sendmsg( thread_id, message) sendet eine Nachricht als Zeichenfolge an den Thread, der mit thread_id angegeben wurde. _thread.getReplID gibt die ID des MainThreads zurück. Achtung! Import des _thread-Moduls nicht vergessen! "light:{}".format(value) konvertiert den Wert in value in einen String und setzt ihn in den Platzhalter {} ein. Der Doppelpunkt hinter "light" wird später bei der Verarbeitung in main.py noch wichtig sein! Ebenso ist wichtig, dass sich weder vor noch nach dem Doppelpunkt Leerzeichen befinden! 4.3.2 - Thread-Messages vom Server in main.py empfangen und verarbeiten Um im MainThread überhaupt Messages empfangen zu können, müssen wir dies in main.py zuerst explizit mit _thread.ReplAcceptMsg(True) erlauben!   msg = _thread.getmsg() speichert - wenn vorhanden - empfangene Nachrichten in msg. msg enthält dann ein Tupel (message_type, sender_id, message), wobei message_type 0 (=None), 1 (=integer) oder 2 (=String) sein kann. sender_id ist die Thread-ID des Senders (wobei hier nur der Server infrage kommt) message ist die Nachricht, die wir vom Server aus geschickt haben, also "light:formData["light"]. Um an den Wert von formData["light"] zu kommen, müssen wir also msg[2] an der Stelle ":" splitten. values = msg[2].split(":") erledigt das für uns, sodass values dann aus ("light", formData["light"] besteht. Doch sollten wir - bevor wir mit dem Wert in formData["light"] die Animation starten noch prüfen, ob wir mit dem Wert wirklich die Animationen steuern. Jedoch ist hier Vorsicht geboten: viel zu oft kommt es aus Gewohnheit vor, dass sich vor oder nach dem Doppelpunkt Leerzeichen einschleichen. Diese kann man aber einfach abtrennen. values[0].strip() entfernt Leerzeichen, die zu Beginn oder am Ende des Strings vorkommen. Die String Verarbeitung in Python ist ein sehr komplexes Thema, weshalb ich mich hier auf die Beschreibung der notwendigsten Funktionen beschränke. Weitere Infos kann man zum Beispiel hier finden: https://docs.python.org/2/library/string.html Auch hier sollten wir vor dem Start einer neuen Animation prüfen, ob bereits eine Animation im Thread läuft. Außerdem ist es zwingend notwendig, vor Start des neuen Threads dem WebServer einige Zeit (2000 ms) zu geben, dass dieser die Response-Seite dem Client (also unserem Computer) zur Verfügung stellen kann, andernfalls führt dies zu kritischen Fehlern. Das bedeutet, das System stürzt ab und startet neu - die Animation kann also nicht gestartet werden Zusammengefasst sieht das dann so aus: _thread.ReplAcceptMsg(True) wait_before_start_thread = 2000 # ... while True: # ... msg = _thread.getmsg() if msg[0] == 2: #true if msg is a String values = msg[2].split(":") if values[0].strip() == "light": if _thread.status(animation_thread) != _thread.TERMINATED: _thread.notify(animation_thread, _thread.EXIT) utime.sleep_ms(wait_before_start_thread) _thread.start_new_thread("animation", animations.start, (values[1].strip(), )) 4.3.3 - Animationen mit einem String starten Nun haben wir wieder mit animations.start (String) wieder eine Funktion in animatons angedeutet, die noch nicht vorhanden ist. Also wechseln wir in animations.py und erstellen unter der Funktion next() die Funktion start(animation_name). In dieser Funktion soll die Animation gestartet werden, die als String in animation_name übergeben wurde. Aus diesem Grund haben wir, um unsere Animationen zu speichern, einen Dictionary angelegt anstelle einer Liste. Nun können wir einfach mit dem Schlüsselwort, gespeichert in animation_name, die Animation starten: def start(animation_name): animation_dict[animation_name]() 4.3.4 - Zwei ähnliche Routinen in einer Funktion zusammenfassen Nun haben wir zwei Routinen in main.py, die im Prinzip ähnliches tun: Wenn eine Animation im Thread läuft wird diese gestoppt, nach einem kurzen Delay dann eine neue im Thread gestartet. Dies geschieht entweder mit animations.next oder mit animations.start. Diese sollten wir in einer Funktion - handleAnimations - zusammenfassen. Nachdem der animation_thread gestoppt wurde, müssen wir zunächst prüfen, ob der Funktion ein Animations-name übergeben wurde (falls die Animation auf der Website ausgewählt wurde) oder nicht (falls sie durch den Touch-Sensor gewechselt wurde). In Abhängigkeit dessen müssen wir nun den Wert von sleep_ms wählen und die Animation mit der richtigen Funktion in animations.py (next oder start) starten. def handleAnimations(animation_name=None): _thread.notify(animation_thread, _thread.EXIT) if animation_name == None: utime.sleep_ms(50) animation_thread = _thread.start_new_thread("animation", animations.next, () ) else: print("starting animation {}".format(animation_name)) utime.sleep_ms(wait_before_start_thread) animation_thread = _thread.start_new_thread("animation", animations.start, (animation_name,)) Nun können wir entsprechende Zeilen in der Touch Sensor-Verarbeitung ersetzen durch: handleAnimations() in der Verarbeitung der Server-Messages durch handleAnimations(values[1]) 5 - RainbowWarrior als Wecker nutzen Nun haben wir gelernt, wie man den LED Strip ansteuert sowie mit demPiezo Speaker Songs abspielt. Selbstverständlich lassen sich diese beidem Elemente auch kombinieren um sie als Wecker einzusetzen. FreeRTOS ist ein Real Time Operating System. Das bedeutet, man kann mit dem ESP auch zeitgesteuerte Operationen ausführen. Die Klasse machine.RTC stellt uns eine Real Time Clock zur Verfügung, mit der wir Datum- und Uhrzeit einstellen können. Sowie uns das Modul utime Zeit-abhängige Funktionen zur Verfügung stellt 5.1 - Der Piezo-Speaker Der Piezo-Speaker ist - einfach ausgedrückt - ein kleiner Lautsprecher, der im Prinzip durch elektrische Signale (deren Frequenz ähnlich aufgebaut ist wie unsere hörbaren Schallwellen) seine Membran zum Schwingen bringt, wodurch ein akustisches Signal entsteht. Er verwandelt Strom in Ton - wie jeder andere Lautsprecher. https://de.wikipedia.org/wiki/Ferroelektrischer_Lautsprecher 5.1.1 - PWM - Pulsweitenmodulation (Pulse-Width-Modulation) Ein PWM ist im Prinzip ein Digital Analog Converter, macht also genau das Gegenteil eines ADCs, den wir für den LDR eingesetzt haben. Leider finde ich keinen Weg, die Arbeitsweise eines PWM in einfachen Worten zu beschreiben, jedoch veranschaulicht diese Grafik aus Wikipedia (https://de.wikipedia.org /wiki/Pulsweitenmodulati on) die Übersetzung von einem digitalen Signal (das ja wie bekannt nur zwischen "ein" und "aus", bzw 1 und 0 unterscheiden kann) eine analoge Sinuswelle werden kann - oder eben anders herum. Wobei man beim Verwandeln eines digitalen in ein analoges Signal nicht mehr wirklich von "Welle" sprechen kann, da diese "Welle" doch irgendwie eckig wird Auf dem Bild kann man oben die Sinuswelle, unten das Digitalsignal erkennen. Das Sägezahn förmige Signal, das über der Sinuswelle liegt, kann man als "Übersetzer" betrachten. So gibt der x -Abstand zwischen den Schnittpunkten zwischen Sinuswelle und Sägezahnsignal vor, wie lange das Digitalsignal auf "ein" stehen muss. 5.1.1.1 - Das PWM-Modul https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo/wiki/pwm pwm = machine.PWM(pin [, freq=f] [, duty=d] [, timer=tm]) erstellt eine Instanz des PWM, wobei     pin die Pin-Nummer ist, an der .. anliegt. Der Pin kann entweder als machine.Pin, oder als Integer-Wert übergeben werden. freq (optional) bezeichnet die Frequenz in Hz, auf der der Piezo beim Start schwingen soll (weshalb es ratsam ist, den Piezo erst zu initialisieren, wenn er auch was abspielen soll). Default: 5 kHz duty (optional) ist der Arbeitszyklus in %. Default: 50% timer (optional) gibt an, ob ein Zeitmesser aktiviert werden soll (default: 0) pwm.freq([freq]) setzt die Frequenz in Hz. Dies hat Auswirkungen auf alle Kanäle, die denselben Timer nutzen Wenn kein Argument übergeben wird gibt diese Funktion die Frequenz zurück, die auf diesem Kanal gespielt wird. pwm.deinit() deinitialisiert den pwm-Pin, Ausgabe auf diesem Pin wird gestoppt. pwm.init([ freq=f] [, duty=d] [, timer=tm]) reinitialisiert pwm. 5.1.2 - Piezo-Speaker mit dem ESP verbinden Um den Piezo-Speaker mit dem ESP zu verbinden, muss man lediglich Vin mit Pin 22 verbinden, sowie GND mit einem GND-Pin am ESP (alternativ mit der GND-Reihe auf dem Breadboard) 5.1.3 - Das RTTTL-Modul RTTTL steht für Ring Tone Text Transfer Language und ist ein verbreitetes Format für Klingeltöne auf Mobiltelefonen. Hier ein Beispiel für den Song "One More Time": 'OneMoreT:d=16,o=5,b=125:4e,4e,4e,4e,4e,4e,8p,4d#.,4e,4e,4e,4e ,4e,4e,8p,4d#.,4e,4e,4e,4e,4e,4e,8p,4d#.,4f#,4f#,4f#,4f#,4f#,4 f#,8f#,4d#.,4e,4e,4e,4e,4e,4e,8p,4d#.,4e,4e,4e,4e,4e,4e,8p,4d# .,1f#,2f#' Der Klingelton muss drei Teile beinhalten, um vom Klingeltonprogramm (oder hier des RTTTL-Moduls) erkannt zu werden: 1. Name des Klingeltons (hier: 'OneMoreT') 2. Standardvorgaben (d=16,o=5,b=125), wobei "d=" die Standardnotenlänge ist. "4" bedeutet, dass jede Note ohne Längenangabe eine Viertelnote ist. "8" würde eine Achtelnote bedeuten usw. "o=" die Standardoktave. Es gibt vier Oktaven im RTTTL-Format. "b=" steht für die Abspielgeschwindigkeit in Schlägen pro Minute. 3. Die Tonangaben. Sie sind durch Komma voneinander getrennt und beinhalten:  (optional) Tonlänge   Notenangabe (c, d e, f, g, a, b, oder p = Pause). Eine Raute hinter der Tonangabe erhöht um einen Halbton. (optional) Oktave Das RTTTL-Modul für MicroPython kann hier heruntergeladen werden, wobei wir lediglich Datei "rtttl.py" brauchen werden : https://github.com/dhylands/upy-rtttl Dieser sollte am besten im Projektordner, wo auch alle bisher erstellten *.py-Files liegen, gespeichert werden. Wie wir beim Betrachten des Dateisystems festgestellt haben, ist der Speicherort für weitere Module auf dem ESP der Ordner "lib". Also müssen wir einen Ordner namens "lib" erstellen und die Datei rtttl.py in diesen kopieren. mkdir /flash/lib cp rtttl.py /flash/lib Weitere Beispielsongs findet man hier: http://www.picaxe.com/RTTTL-Ringtones-for-Tune-Command/ http://mines.lumpylumpy.com/Electronics/Computers/Software/Cpp/MFC/ RingTones.RTTTL 5.1.4 - Real Time Clock - das RTC Modul Mit dem RTC-Modul können wir die Systemzeit des ESP einstellen. Zudem haben wir die Möglichkeit - sofern wir im Station-Mode mit dem Internet verbunden sind - die Uhrzeit im Internet zu synchronisieren (so, wie es auch unsere Mobiltelefone machen). Außerdem können wir den ESP in den sogenannten "deepsleep"-Modus versetzen. Das bedeutet, dass viele Funktionen heruntergefahren werden und somit weniger Strom verbraucht wird. Selbstverständlich stellt es auch Möglichkeiten zur Verfügung, den ESP wieder aus dem Tiefschlaf zu wecken! Die Modulbeschreibung ist hier zu finden: https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo/wiki/rtc Die wichtigsten Funktionen, um den ESP als Wecker zu nutzen, sind:  rtc = machine.RTC() erstellt eine Instanz der RTC   rtc.init(date) lässt uns die Systemzeit einstellen, wobei für Date ein Tupel erwartet wird: (year, month, day [,hour [,minute [, second ]]]) rtc.now() gibt uns die aktuelle Zeit als Tupel zurück: (year, month, day, hour, minute, second) Um die Zeit im Internet zu synchronisieren sollte vorab eine Prüfung stattfinden, ob der ESP im Station-Mode verbunden ist.  rtc.ntp_sync(server [,update_period] [,tz]) wobei als Server "de.pool.ntp.org" genutzt werden kann. 5.1.5 - Zeitabhängige Funktionen - das utime-Modul Das utime-Modul stellt ebenso Funktionen zur Verfügung, mit denen man die aktuelle Zeit ausgeben kann. Außerdem Zeitintervalle messen und Delays ausführen. https://docs.micropython.org/en/latest/pyboard/library/utime.html  utime.localtime([secs]) konvertiert die Zeit, angegeben in Sekunden (secs) in ein Tupel aus 8 Elementen (year, month, mday, hour, minute, second, weekday, yearday). Wenn kein Argument übergeben wird, wird die aktuelle Systemzeit genutzt. o year - als Jahrtausendangabe (z.B. 2014). o month (1-12) o mday (1-31) o hour (0-23) o minute (0-59) o second (0-59) o weekday (0-6 für Mo-So) o yearday (1-366)  utime.mktime(time_tuple) gibt - Gegenteilig zu localtime - für ein vollständiges Tupel aus 8 Elementen (year, month, mday, hour, minute, second, weekday, yearday) die Anzahl der Sekunden zwischen dem 01.01.1970 und der dateTime, die im Tupel üergeben wurde, zurück. utime.sleep_ms(milliseconds) führt ein Delay für die Angegebene Zeit in Millisekunden aus utime.sleep_us(microseconds) führt ein Delay für die Angegebene Zeit in Mikrosekunden aus utime.ticks_ms() gibt eine steigende Zahl an Millisekunden zurück - der Referenzpunkt ist allerdings willkürlich gesetzt. Diese Funktion wird zum Beispiel eingesetzt, um ein Delay zu erstellen. utime.ticks_add(ticks, delta) führt eine Addition mit Ticks aus, wobei ticks vorher mit ticks_ms(, ticks_us oder ticks_cpu - in Modulbeschreibung zu      finden) erstellt werden muss. delta kann eine positive oder negative Integer sein) utime.ticks_diff(ticks1, ticks2) gibt die Zeitdifferenz zwischen zwei ticksWerten zurück. Normale Addition bzw Subtraktion von ticks-Werten sollten nicht ausgeführt werden, da dies zu inkorrekten Ergebnissen führen kann. o utime.time() gibt die Anzahl der Sekunden zurück, die zwischen dem 01.01.1970 und der eingestellten Systemzeit vergangen sind. 5.1.5 - Songs mit dem PWM abspielen Um die Songs zu speichern und abzuspielen, erstellen wir eine neue Datei namens songs.py. Wir müssen nicht immer das komplette machine-Modul einbinden, wenn wir lediglich Pin und PWM daraus nutzen werden. Ebenso benötigen wir aus rtttl lediglich die Klasse RTTTL. Außerdem wird noch das _thread-Modul importiert, da auch das Abspielen der Songs in einem Thread laufen wird. Außerdem können wir gleich den piezo_pin (22) definieren und PWM kurz initialisieren, um ihn sofort wieder zu deinitialisieren. Dies geschieht aus dem Grund, dass unsere PWM-Instanz auch von beiden Funktionen aus verändert werden können. Zudem benötigen wir noch eine Variable, die auf True gesetzt wird, sobald eine Benachrichtigung zum Abbruch des Threads kommt: from machine import Pin, PWM from rtttl import RTTTL import _thread piezo_pin = 22 piezo = PWM(piezo_pin) piezo.deinit() abort_playback = False Unsere Songs speichern wir in einer Liste namens SONGS. Wie bereits bekannt müssen Elemente einer Liste durch Komma getrennt werden. Aufgrund der Länge der verwendeten Strings und der Tatsache, dass sie viel zu lang sind, um auf dieser Seite einzeilig dargestellt zu werden, kann dies gerne übersehen werden: SONGS = [ 'OneMoreT:d=16,o=5,b=125:4e,4e,4e,4e,4e,4e,8p,4d#.,4e,4e, 4e,4e,4e,4e, 8p,4d#.,4e,4e,4e,4e,4e,4e,8p,4d#.,4f#,4f#,4f#,4f#,4f#,4f# ,8f#,4d#., 4e,4e,4e,4e,4e,4e,8p,4d#.,4e,4e,4e,4e,4e,4e,8p,4d#.,1f#,2 f#', 'MarioTitle:d=4,o=5,b=125:8d7,8d7,8d7,8d6,8d7,8d7,8d7,8d6 ,2d#7,8d7,p, 32p,d6,8b6,8b6,8b6,8d6,8b6,8b6,8b6,8d6,8b6,8b6,8b6,16b6,1 6c7,b6,8a6, 8d6,8a6,8a6,8a6,8d6,8a6,8a6,8a6,8d6,8a6,8a6,8a6,16a6,16b6 ,a6,8g6,8d6, 8b6,8b6,8b6,8d6,8b6,8b6,8b6,8d6,8b6,8b6,8b6,16a6,16b6,c7, e7,8d7,8d7, 8d7,8d6,8c7,8c7,8c7,8f#6,2g6', 'Tetris:d=4,o=5,b=160:e6,8b,8c6,8d6,16e6,16d6,8c6,8b,a8a, c6,e6,8d6, 8c6,b,8b,8c6,d6,e6,c6,a,2a,8p,d6,8f6,a6,8g6,8f6,e6,8e6,8c 6,e6,8d6, 8c6,b,8b,8c6,d6,e6,c6,a,a' ] Wir werden insgesamt drei Funktionen benötigen, wovon nur eine vom MainThread aus aufgerufen wird. Die vom MainThread aufzurufende Funktion wird aus SONGS den angeforderten Song wählen und in einer separaten Variable speichern. Anschließend wird mit dieser Variable als Argument eine Instanz von RTTTL erstellt. Nun wird für jede Note, die in diesem Song vorhanden ist, die nächste Funktion play_tone aufgerufen, die die Note dann abspielt. In dieser Schleife sollte auch geprüft werden, ob der Thread abgebrochen werden soll: def find_song(name): """ find_song(name) searches in SONGS list for song name and plays its tones with the play_tone function name: name of the song to search for """ global abort_playback abort_playback = False for song in SONGS: song_name = song.split(":")[0] if song_name == name: tune = RTTTL(song) for freq, msec in tune.notes(): play_tone(freq, msec) if abort_playback: return piezo.deinit() Zwei Ausdrücke, die wir - zumindest so - noch nicht besprochen haben:   song_name = song.split(":")[0] - splittet den String an der Stelle, an der der Doppelpunkt vorkommt. Dies funktioniert natürlich ebenso, wenn mehr als ein Doppelpunkt im String vorhanden ist. [0] beinhaltet dann den Namen, [1] die Standardvorgaben und [2] die Tonangaben. for freq, msec in tune.notes() deutet darauf hin, dass von der Funktion tune.notes() zwei Objekte zurückgegeben werden: freq für die Frequenz und msec für die Dauer in ms. somit kann man mit dieser for-Schleife zwei Werte parallel verarbeiten. abort_playback ist - wie der Name schon sagt, eine Variable, in der festgehalten wird, ob die Wiedergabe abgebrochen werden soll oder nicht. Dies wird in play_tone geprüft play _tone(freq, ms) muss vor der Funktion find_song() platziert werden. Hier wird für jede einzelne Note ein Ton mittels PWM erzeugt. Anstelle von utime.sleep_ms(ms) können wir - analog zu animations.py - waitForExitNotification(ms) einsetzen, die im darauf folgenden Schritt erstellt wird: def play_tone(freq, msec): """play_tone(freq, msec) plays a single tone on piezo buzzer freq: frequency of the tone msec: duration in millis """ global piezo global abort_playback print('freq = {:6.1f} msec = {:6.1f}'.format(freq, msec)) if freq > 0: piezo.freq(int(freq)) piezo.duty(50) ntf0 =waitForExitNotification(int(msec *0.9)) piezo.duty(0) ntf1 = waitForExitNotification(int(msec * 0.1)) if (ntf0 or ntf1): abort_playback = True piezo.deinit() Hier wird der Wert von ms gesplittet: zuerst wird gewartet, bis 90% des ms-Wertes vergangen sind, um dann duty auf 0 zu setzen. Anschließend wird die letzten 10% von ms abgewartet. Um nicht versehentlich eine Benachrichtigung zu überschreiben, werden diese in zwei verschiedene Variablen gespeichert. ntf0 or ntf1 ist eine logische Operation. Sie gibt genau dann True zurück, wenn mindestens eine der beiden Operatoren (ntf0 oder ntf1) True ist. Somit wird auch keine Abbruchbedingung übersehen. Zuletzt benötigen wir noch eine Funktion, die die Abbruchbedingung prüft: def waitForExitNotification(timeout): """ waitForExitNotification(timeout) uses _thread.wait(timeout) to sleep for amount of ms saved in timeout, checks notfication for _thread.EXIT notification. Returns True if _thread.EXIT notification is recieved, else returns False timeout: (ms) time in ms used for _thread.wait() return: (boolean) True if _thread.EXIT is recieved, else False. """ ntf = _thread.wait(timeout) if ntf == _thread.EXIT: return True return False return False muss hier nicht zwingend in einem else-Block stehen. Das returnStatement im if-Blockbewirkt, dass diese Funktion mit dieser Zeile verlassen wird. Wenn also die Bedingung (ntf == _thread.EXIT) zutrifft, wird return False gar nicht mehr ausgeführt. 5.2 - Dropdown Menü für Songs in die Webseite einfügen Um den Song auf der Webseite auswählen zu können verfahren wir analog wie mit den LED Animationen. Um uns Arbeit zu sparen, können wir den gesamten Bereich innerhalb der form-Tags kopieren und unterhalb des kopierten Bereichs wieder einfügen. Um etwas mehr Abstand zwischen den beiden Menüs zu schaffen, sollte man
Tags einfügen. Diese bewirken einen Zeilenumsprung. Hier darf gerne getestet werden, was optisch akzeptabel ist. h3 sollten wir nun selbstverständlich anpassen: „Song auswählen“ passt hier besser! als nächstes muss im select-Tag der name angepasst werden: name="song" Nun müssen wir noch unsere Songs ins Dropdown Menü einfügen. Hier sollten wir - wie auch bei den LED Animationen - darauf achten, dass unter value der Name steht, der in der SONGS-Liste in songs.py zu finden ist. Dies spart uns unnötige Abfragen. Der Name, der auf der Seite gezeigt werden soll, ist dafür immer noch frei wählbar. Hier nun der neu eingefügte Part in index.html:

Song auswählen:

Übrigens: man muss die HTML-Seite nicht zwingend zuerst auf den ESP kopieren um sie anschauen zu können. Da sie sich gemeinsam mit ihren zugehörigen CSSSeiten im Ordner www befindet, kann man diese einfach mit einem Doppelklick im Browser öffnen. 5.2.1 - Verarbeitung der Daten auf dem Webserver Auch hier kann man analog zu den Animationen verfahren: in webSrv.py sollte nun nicht nur nach "light" geprüft werden, sondern auch nach "song". Oder aber man verzichtet auf diesen Schritt und überlässt diese Unterscheidung dem MainThread! Dazu kann die Zeile: if "light" in formData: ersetzt werden durch: if formData: Das bedeutet, dass die Thread-Message nur gesendet wird, wenn Daten empfangen wurden. Thread-Message sollte entweder aus einem String, oder einem Integer-Wert bestehen. Ein Dictionary ist allerdings weder das eine, noch das andere! Dafür kann man einen Dictionary sehr einfach mit str(dict) in einen String verwandeln! und einen String (sofern er den Aufbau eines Dictionarys hat) ebenso einfach mit eval(str) wieder in einen Dictionary! if formData: message = str(formData) _thread.sendmsg(_thread.getReplID(), message) konvertiert nun den vom webServer empfangenen Dictionary in einen String und sendet diesen an den MainThread. In main.py lassen wir uns nun ausgeben, was der Server uns geschickt hat: msg = _thread.getmsg( if msg[1] == 2: print(msg[2]) >>> {'light': 'crossing_dots', 'sound': 'Song+auswählen+...'} Oh! Dies bedeutet, dass nun - unabhängig davon, welcher Button von beiden gedrückt wurde, beide Werte übergeben werden! Dies liegt wohl daran, dass sich beide innerhalb einer Form befinden! Man kann nun entweder eine separate Form für jedes Menü erstellen - oder aber wir versehen die Buttons mit name und value, wodurch der angeklickte Button diese Werte ebenso übergibt. Anhand diesen wir entscheiden, wie mit diesen Daten weiter verfahren wird. Für beide Buttons setzen wir also name="button" ein. so können wir später einfach den Wert prüfen, der an der Stelle "button" ist. Als value setzen wir "light" bei den Animationen und "sound" bei den songs. Dann sieht der Output so aus: > {'light': 'crossing_dots', 'sound': 'Song+auswählen+...', 'button': 'light'} 5.2.2 - Verarbeitung der Daten in main Nun haben wir uns eine Routine gebaut, die sehr Variabel mit allen möglichen Inputs umgehen kann. Es fehlt lediglich noch die Verarbeitung in main.py. _thread.getmsg()[2] liefert uns einen Dictionary als String, den wir nun wieder in einen Dictionary umwandeln müssen. Anschließend müssen wir prüfen, ob der Button mit dem value "light" gedrückt wurde. dann können wir handleAnimationThread mit dem Wert, der in "light" steht starten: # read threadMessage from microWebSrv msg = _thread.getmsg() # check if message is String if msg[0] == 2: # convert String in dictionary values_dict = eval(msg[2]) call function according to value of "button" if values_dict["button"] == "light: handleAnimationThread(values_dict["light"]) # Analogverfahren wir mit "sound": elif values_dict["button"] == "sound": handleSoundThread(values_dict["sound"]) Ohne die Funktion handleSoundThread(song) zu definieren, können wir allerdings nicht fortfahren: sound_thread = 100 def handleSoundThread(song): """ stopps running sound thread and starts a new thread with value saved in 'song' song: (String) name of the song to start """ global sound_thread _thread.notify(sound_thread, _thread.EXIT) sound_thread = _thread.start_new_thread("sound", songs.find_song, (song, )) Fertig für den Test! Wenn alles fehlerfrei eingefügt und auf den ESP kopiert wurde, kann man nun nach einem Neustart den Song von der Webseite aus abspielen lassen. Leider aber stößt der ESP an seine Grenzen, wenn man rechenintensive Animationen zeitgleich mit einem Song laufen lässt. In diesem Fall ist es möglich, dass Song und Animation merkbar langsamer abgespielt werden. 5.3 - Systemzeit auf der Webseite anzeigen Um lediglich die aktuelle Zeit anzuzeigen reicht uns das utime-Modul. Nachdem dieses in webSrv.py eingebunden wurde, kann die aktuelle Systemzeit als Tupel in der Variable datetime gespeichert werden: import utime datetime = utime.localtime() Damit bei jedem Aufruf der Webseite die aktuelle Systemzeit genutzt wird, muss datetime = utime.localtime() innerhalb der Route-Handler Funktionen stehen. Zur Erinnerung, das Tupel besteht aus 8 Elementen: (year, month, mday, hour, minute, second, weekday, yearday) Wollen wir nun aus diesem Tupel z.B. eine Ausgabe am Terminal machen, die nur die Uhrzeit (Stunden und Minuten) getrennt durch einen Doppelpunkt - anzeigt, müssen wir uns zuerst etwas mit dem Thema String Formatierung beschäftigen: 5.3.1 - String Formatierung Bisher haben wir die print-Ausgabe nur genutzt, um entweder Integer-Variablen oder Texte auszugeben. Man kann diese beiden Ausgaben aber auch kombinieren! Hier ein Beispiel für die Ausgabe mit Text und Variable anhand der Print-Ausgabe. Zuerst werden wir das Alter des Nutzers in Repl einlesen, anschließend einen Text inklusive dem eingegebenen Alter ausgeben. >>> age = input("Alter? ") Dadurch erfolgt in Repl die Ausgabe 'Alter? ', bei der man dann das Alter (z.B. 22) eingeben kann. Dieses wird nach der Bestätigung mit Enter in der Variable age gespeichert: Alter? 22 Nun wollen wir das Alter in einen Text einfügen und ausgeben: > print("Alter: {} Jahre".format(age)) Alter ist 22 Jahre String.format(var) konvertiert dabei die Variable var in einen String und fügt sie in den Platzhalter {} ein. Wollen wir nun wissen, um welchen Datentyp es sich bei "age" handelt, schreiben wir: >>> type(age) Was bedeutet, dass es sich um einen String handelt. Um mit dieser Eingabe Berechnungen ausführen zu können, bräuchten wir allerdings einen Integer-Wert. mit int(age) lässt sich der String in age in einen Integer-Wert konvertieren. Dies geht ebenso direkt mit der Eingabe: >>>age=int(input("Alter?")) > Alter? 22 > type(age) 5.3.1.1 - Arbeiten mit mehreren Platzhaltern Nehmen wir an, wir haben Geschwister im Alter von 12, 14 und 18 Jahren (Alter gespeichert in age1, age2, age3) und wollen diese in einen Text einfügen, gehen wir folgendermaßen vor: > print("Meine Geschwister sind {}, {} und {} Jahre alt".format(age1, age2, age3) Meine Geschwister sind 12, 14 und 18 Jahre alt Wobei in die Platzhalter der Reihe nach die Werte in format() eingetragen werden. Allerdings können wir auch die Reihenfolge der Ausgabe anpassen, ohne die Reihenfolge in format() zu verändern: > print("Meine grosse Schwester ist {2} Jahre alt, mein kleiner Bruder {0} und meine kleine Schwester {1}".format(age1, age2, age3)) Meine grosse Schwester ist 18 Jahre alt, mein kleiner Bruder 12 und meine kleine Schwester 14 Nehmen wir nun an, wir haben eine Variable time, in der die Zeit als Tupel (h, m) gespeichert ist. Derzeit ist es kurz nach 9. Die Ausgabe der Uhrzeit in einem String würde dann folgendermaßen aussehen: > print("Zeit: {}:{} Uhr".format(time[0], time[1]) Zeit: 9:6 Uhr Leider sieht dies schrecklich aus, da wir gewohnt sind, dass die Uhrzeit in dieser Darstellung immer zweistellig für Stunde und Minute ist. Diese Darstellung erreichen wir folgendermaßen: > print("Uhrzeit: {0:02d}:{1:02d} Uhr".format(time[0], time[1])) Uhrzeit: 09:06 Uhr Wobei 02 bedeutet, dass die Ausgabe mindestens zweistellig sein muss, bei kleineren Werten soll eine 0 vorangestellt werden. Das d bedeutet, dass es sich bei diesem Wert um eine Dezimalzahl handelt. Hier ist die Position (0: oder 1:) zwingend anzugeben! Man kann bei der Ausgabe ebenso mit Schlüsselwortparametern arbeiten: >>>print("die Hauptstadt von {province} ist{capital}.".format("province" : "Hessen", "capital" : "Wiesbaden")) die Hauptstadt von Hessen ist Wiesbaden. Diese Angaben gelten nicht nur für die print-Ausgabe, sondern sind ebenso auch auf Strings anwendbar: > month = "April" > text = "Mein Geburtsmonat ist {}" > text.format(month) > print(text) Mein Geburtsmonat ist April Weitere Anwendungsbeispiele findet man im Python-Forum: https://www.python-kurs.eu/python3_formatierte_ausgabe.php 5.3.2 - Formatierung der Systemzeit Um also die Systemzeit auf der Webseite anzeigen zu können, müssen wir auf dieser erst einmal Platzhalter einfügen. Anschließend müssen wir dafür sorgen, dass uns die HTML-Datei als String in webSrv.py vorliegt, da wir format() nur auf einen String anwenden können. Zum Schluss müssen wir noch httpResponse unseres RouteHandlers anpassen. Die Zeitausgabe auf der Webseite soll folgendermaßen formatiert werden: 22.09.2018 15:05 Uhr Unser Tupel besteht aus year (0), month (1), mday (2), hour (3), minute (4), second (5), weekday (6), yearday (7) Zur besseren Übersicht können wir nun die Zeit und das Datum in unserer gewohnten Reihenfolge in einen Dictionary in webSrv.py speichern: datetime_dict = { "year" : datetime[0], "month" : datetime[1], "mday" : datetime[2], "hour": datetime[3], "minute" : datetime[4], "second" : datetime[5], "weekday" : datetime[6], "yearday" : datetime[7]} Nun fügen wir in index.html die entsprechenden Platzhalter ein. Dies erreichen wir, indem wir einen neuen "Paragrafen" mit

(text)

einfügen. Nur Text innerhalb eines Tags wird auch auf der Webseite dargestellt! Noch vor dem ersten
-Tag fügen wir also zuerst einen öffnenden

-Tag, dann unsere Platzhalter inklusive Text, danach den schließenden Tag

ein:

{mday:02d}.{month:02d}.{year:04d} {hour:02d}: {minute:02d} Uhr

Die zwei Textfelder und der Button in der Form werden in einem
-Tag zusammengefasst
Innerhalb dieses
-Tags fügen wir nun zuerst unsere Textfelder ein. Textfelder werden durch -Tags erstellt. Zu jedem -Tag gehört noch ein

Navigation menu