AVR goes ESP mit MicroPython - Teil 5

In der ersten Folge dieser Projektreihe hatte ich bedauert, dass der Nano V3 nicht WLAN-fähig ist. Um das zu ändern, wurden zunächst einmal ein ESP32 und ein ESP8266 über NRF24L01-Module über Funk verbunden. Daraus entstand schließlich ein MicroPython-Modul, nRF24simple.py. In der zweiten Folge brachte ich den Nano dazu, sich mit dem ESP8266 auch über NRF24L01-Module zu unterhalten. Folge 3 beschreibt die Ankopplung eines Nano V3 an den minimalistischen ESP8266-01 via I2C-Bus. Damit war die Grundlage geschaffen, den ATmega328 Mikrocontroller auch ans WLAN zu bringen. Das Programm esp_i2c_master.py stellte Funktionen zusammen, die Aufträge an den Nano senden konnten, Parallelport auslesen und beschreiben, PWM-Signale ausgeben und Spannungen messen. Folge 4 brachte das Modul arduino_i2c.py, in welchem die bisherigen Funktionen integriert, verbessert und durch weitere Methoden ergänzt wurden.

Heute werden wir das Projekt um die WLAN-Schnittstelle erweitern. Damit willkommen zur Reihe

MicroPython auf dem ESP32 und ESP8266

heute

AVR goes ESP mit MicropPython (Teil 5) - WLAN

Um ein Gerät WLAN-fähig zu machen, bedarf es zweier Stufen über die "Economy-Class" hinaus. Zunächst muss die Verbindung zu einem Accesspoint hergestellt werden. Alternativ kann ein ESP8266 oder ein ESP32 auch selbst einen Accesspoint darstellen. Das hat Vor- und Nachteile. Dazu kommen wir später.

Dass ein Gerät sich in einem Netzwerk angemeldet hat, reicht aber noch nicht. Wir möchten ja auf die Infrastruktur des Geräts zugreifen. Dazu bedarf es einer Client-Server-Struktur. In unserem Fall wird der ESP8266-01 den Server und Vermittler zum AVR, dem Nano, spielen. Als Client werden wir in einem ersten Ansatz einen Web-Browser benutzen. Der Server wird eine Webseite mit Formularen erzeugen, die wir ausfüllen und an den Server zurücksenden.

Abbildung 1: Die Stufen zur WLAN-Klasse

Abbildung 1: Die Stufen zur WLAN-Klasse

Alternativ kann man in einem zweiten Ansatz auch einen UDP-Server bauen, den man zum Beispiel mit einer Handy-App anfunken kann.

Wenn man sich für den eigenen Accesspoint auf dem ESP8266-01 entscheidet, hat das den Vorteil, dass man keine lokale Netzwerkstruktur mit einem Router benötigt. Man stellt dann ja selbst eine. Ein Nachteil besteht darin, dass kein Internetzugang besteht. Ein weiterer Nachteil ist der, dass sich das Handy erst auf dem ESP8266-01-Accesspoint anmelden muss.

Hardware

Die Hardware ist immer noch unverändert diejenige aus der vierten Folge. PC oder Handy zähle ich nicht mit auf, obwohl es ohne nicht gehen wird.

1

Nano V3.0 CH340 Chip + Breadboardadapter für ESP-01 oder

1

ESP8266 01 esp-01 Wlan WiFi Modul mit Breadboardadapter

1

KY-009 RGB LED SMD Modul

2

KY-004 Taster Modul

1

Logic Level Converter TXS0108E 8 Kanal (*)

1

MB-102 Breadboard Steckbrett mit 830 Kontakten

1

FT232-AZ USB zu TTL Serial Adapter für 3,3V und 5V

1

4x4 Matrix Array Keypad Tastenfeld Tastatur oder

4x4 Matrix Keypad Tastatur

1

LED (blau oder grün, passend zum Widerstand 2,2kΩ

3

Widerstand 2,2kΩ

1

Widerstand 560Ω

1

Widerstand 10kΩ

diverse

Jumperkabel

2

passende USB-Kabel

1

Batterie 4,5V oder 5V-Steckernetzteil

(*) alternative Möglichkeit weiter unten im Text

Auch an der Schaltung hat sich nichts verändert.

Abbildung 2: IO-Ports und PWM-LED-Steuerung

Abbildung 2: IO-Ports und PWM-LED-Steuerung

Ich will nicht versäumen wieder darauf hinzuweisen, dass der ESP8266-01 auf seinen Anschlüssen keine Spannungen größer als 3,6V ausstehen kann. Damit er sich nicht ins Nirvana verabschiedet, muss der Jumper auf dem FTDI-Adapter unbedingt auf 3,3V gejumpert sein und die I2C-Leitungen müssen über einen Level-Shifter laufen, der 5V in 3,3V und umgekehrt umsetzen kann. Eine Ersatzschaltung dafür, mit einzelnen Transistoren, finden sie in Folge 4. Wenn später im Produktionssystem der Nano V3 auch mit 3.3V betrieben wird, kann der Pegelwandler entfallen.

Abbildung 3: Nano V3 als I2C-Slave - Anwendungsschaltung für 3.3V

Abbildung 3: Nano V3 als I2C-Slave - Anwendungsschaltung für 3.3V

Auch in dieser Episode sollen die Ports des ATMega328 Mikrocontrollers wieder in ihrer jeweiligen gesamten Breite angesprochen werden können. Für die Umsetzung dieses Vorhabens benutzen wir die Klasse ARDINO aus dem Modul arduino_i2c.py. Hier noch einmal die Zusammenfassung der Bezeichnungen von ATMEL AVR und beim Nano V3.

AVR Port

PD0

RXD

PD1

TXD

PD2

PD3

PD4

PD5

PD6

PD7

Nano V3

D0

RXD

D1

TXD

D2

D3

D4

D5

D6

D7

 

AVR Port

PB0

PB1

PB2

PB3

PB4

PB5

Nano V3

D8

D9

D10

D11

D12

D13

 

AVR Port

PC0

PC1

PC2

PC3

PC4

PC5

 

 

Nano V3

A0

A1

A2

A3

A4

A5

A6*

A7*

* Nur TQFP-Variante und nur auch dort nur analoge Eingänge

Jede Portgruppe besitzt drei Register, ein Ausgangsregister genannt PORT, ein Eingangsregister PIN und ein Datenrichtungsregister DDR. Unser Ziel ist es, alle drei Arten in ganzer Breite zu beschreiben und abzufragen. Ebenfalls sollen einzelne, gezielte Bitoperationen möglich sein. Kommandos und Daten wandern dabei über den I2C-Bus, der ESP8266-01 ist der Auftraggeber, also der Master, der die SCL-Takte sendet.

Die Gruppe C tanzt etwas aus der Reihe. Die C-Pins dienen neben ihrer Funktion als digitale Ports auch als Eingänge des analogen Mutiplexers, der seinerseits die Signale dem ADC (Analog-Digital-Converter) zuführt. Den Eingang A3 werden wir heute in diesem Sinn einsetzen. Wir verbinden ihn mit der Betriebsspannung des ESP8266-01. A4 und A5, alias PC4 und PC5, stellen den Anschluss zur I2C-Hardware her. Auf dem Nano V3 sind die Funktionen dieser Schnittstelle durch Hardware realisiert, die I2C-Schnittstelle des ESP8266-01 ist eine Softwarelösung.

Die RGB-LED schließen wir über drei Widerstände an IO-Leitungen von Port C an. Dafür dienen die Pins PC0 (blau), PC1 (grün) und PC2 (rot). Die einzelne LED an Port PD3 liegt an einem PWM-Ausgang und kann daher gedimmt werden.

Die Software

Für das Flashen und die Programmierung des ESP32:

Thonny oder

µPyCraft

Für den Nano V3:

Arduino-IDE

arduino_als_slave.ino Zur Kommunikation mit dem ESP8266-01 und zum Abarbeiten von Kommandos

Verwendete Firmware für den ESP8266/ESP32:

MicropythonFirmware

Bitte eine Stable-Version aussuchen (ESP8266-01 mit 1MB Version 1.18 Stand: 05.03.2022)

Die MicroPython-Programme zum Projekt:

arduino_i2c.py: Modul mit der Klasse ARDUINO

ardutest.py : Testprogramm für das Modul

arduino_goes_wlan_TCP.py: Zur Kommunikation mit dem Arduino via Browser

arduino_goes_wlan_UDP.py: Zur Kommunikation mit dem Arduino via Handy-App

Sonstige Software:

Packet Sender Downloadseite

Packet Sender Windows Install Version

Packet Sender Windows Portable

Packet Sender Linux

MicroPython - Sprache - Module und Programme

Zur Installation von Thonny finden Sie hier eine ausführliche Anleitung (english version). Darin gibt es auch eine Beschreibung, wie die MicropythonFirmware (Stand 05.02.2022) auf den ESP-Chip gebrannt wird.

MicroPython ist eine Interpretersprache. Der Hauptunterschied zur Arduino-IDE, wo Sie stets und ausschließlich ganze Programme flashen, ist der, dass Sie die MicroPython-Firmware nur einmal zu Beginn auf den ESP32 flashen müssen, damit der Controller MicroPython-Anweisungen versteht. Sie können dazu Thonny, µPyCraft oder esptool.py benutzen. Für Thonny habe ich den Vorgang hier beschrieben.

An dieser Stelle ein paar Takte zum Flashen des ESP8266-01. Der hat nämlich, anders als seine größeren Geschwister, keine Flash-Automatik an Bord. Hier ist Handarbeit gefragt.

Der Flashvorgang gliedert sich in zwei Teile, erstens Flash-Speicher löschen und zweitens Firmware übertragen. Die folgende Liste ist ein Auszug aus der Beschreibung für den Flashvorgang:

    a) In Thonny die Vorbereitungen erledigen
    b) Reset- und Flash-Taste drücken
    c) In Thonny den Flashvorgang starten
    d) Reset-Taste lösen, Flash-Taste halten, bis der Fortschritt angezeigt wird
    e) Flash-Taste lösen
    f) Warten bis erneut der Zugriff auf die COM-Schnittstelle gemeldet wird
    g) Dann erneut die Punkte b) bis f) durchlaufen und
    h) abschließend das Installer-Fenster schließen und die Options mit OK beenden

    Sobald die Firmware geflasht ist, können Sie sich zwanglos mit Ihrem Controller im Zwiegespräch unterhalten, einzelne Befehle testen und sofort die Antwort sehen, ohne vorher ein ganzes Programm kompilieren und übertragen zu müssen. Genau das stört mich nämlich an der Arduino-IDE. Man spart einfach enorm Zeit, wenn man einfache Tests der Syntax und der Hardware bis hin zum Ausprobieren und Verfeinern von Funktionen und ganzen Programmteilen über die Kommandozeile vorab prüfen kann, bevor man ein Programm daraus strickt. Zu diesem Zweck erstelle ich auch gerne immer wieder kleine Testprogramme. Als eine Art Makro fassen sie wiederkehrende Befehle zusammen. Aus solchen Programmfragmenten entwickeln sich dann mitunter ganze Anwendungen.

    Autostart

    Soll das Programm autonom mit dem Einschalten des Controllers starten, kopieren Sie den Programmtext in eine neu angelegte Blankodatei. Speichern Sie diese Datei unter boot.py im Workspace ab und laden Sie sie zum ESP-Chip hoch. Beim nächsten Reset oder Einschalten startet das Programm automatisch.

    Programme testen

    Manuell werden Programme aus dem aktuellen Editorfenster in der Thonny-IDE über die Taste F5 gestartet. Das geht schneller als der Mausklick auf den Startbutton, oder über das Menü Run. Lediglich die im Programm verwendeten Module müssen sich im Flash des ESP32 befinden.

    Zwischendurch doch mal wieder Arduino-IDE?

    Sollten Sie den Controller später wieder zusammen mit der Arduino-IDE verwenden wollen, flashen Sie das Programm einfach in gewohnter Weise. Allerdings hat der ESP32/ESP8266 dann vergessen, dass er jemals MicroPython gesprochen hat. Umgekehrt kann jeder Espressif-Chip, der ein kompiliertes Programm aus der Arduino-IDE oder die AT-Firmware oder LUA oder … enthält, problemlos mit der MicroPython-Firmware versehen werden. Der Vorgang ist immer so, wie hier beschrieben.

    Das XMitter-Programm und der Server-Baukasten

    Für den WLAN-Zugriff meiner ESPs habe ich mir eine Reihe von rudimentären Python-Scripts in einem Verzeichnis zusammengestellt.

    accesspoint.py, multiple_WLANs.py, single_WLAN.py, TCP-webserver.py, UDP-Server_blank.py, UDP-Server.py

    Sie haben so halb den Charakter von Modulen, werden aber dennoch nicht als solche verwendet, weil sie von Fall zu Fall doch gewisse Änderungen oder Anpassungen brauchen.

    Beginnen wir mit der Zusammenstellung zu einem Programm. An den Beginn setzen wir als Basis das Testprogramm aus Folge 4, ardutest.py, das wir auch sofort ein wenig erweitern. Vom Modul machine brauchen wir die Bedienung der I2C-Schnittstelle und Pin. Von arduino_i2c holen wir uns alles, auch die dort definierten Konstanten in unseren Namensraum (aka Scope).

     # arduino_goes_wlan_TCP.py
     # Rev1.1 - 02.03.2022
     from machine import SoftI2C, Pin
     from arduino_i2c import *
     
     SCL=Pin(2)
     SDA=Pin(0)
     i2c=SoftI2C(scl=SCL, sda=SDA, freq=100000)
     a=ARDUINO(i2c,u0=4.73)
     a.writeIO(DDR,C,7)
     rt=2; gn=1; bl=0

    Die Auswahl an I2C-Pins ist nicht sehr groß: 0 und 2, es gibt nur die beiden. Damit instanziieren wir die Schnittstelle und gleich darauf ein ARDUINO-Objekt namens a. Das benutzen wir auch sofort, um die Bits DDRC0, DDRC1 und DDRC2 von PORTC auf Ausgang zu programmieren. In der Folgezeile ordnen wir die Farben zu.

    Der WLAN-Zugang

    Mein Modul single_WLAN.py muss leichte Änderungen hinnehmen, weil ich wegen chronischem Pin-Mangel keine RGB-LED am ESP8266-01 direkt betreiben kann. Was übriggeblieben ist, sieht wie folgt aus. Wir starten mit ein paar weiteren Importen. Der Rest ist reichlich mit Kommentaren versehen, sodass weitere Ausführungen wohl nicht nötig sind. Nur eins, vergessen Sie nicht Ihre eigenen Credentials für den Zugang zum WLAN-Router bei mySSID und myPass einzutragen.

     # Baustein für Kontakversuche zu einem WLAN-Router
     import os,sys       # System- und Dateianweisungen    
     from machine import reset
     from time import sleep, ticks_ms
     import network
     
     
     def hexMac(byteMac):
         """
        Die Funktion hexMAC nimmt die MAC-Adresse im Bytecode  
        entgegen und bildet daraus einen String fuer die Rueckgabe
        """
         macString =""
         for i in range(0,len(byteMac)):    
             macString += hex(byteMac[i])[2:]
             if i <len(byteMac)-1 :          
                 macString +="-"
         return macString
     
     # ***************** Connect to WLAN **********************
     connectStatus = {
         0: "STAT_IDLE",
         1: "STAT_CONNECTING",
         5: "STAT_GOT_IP",
         2:  "STAT_WRONG_PASSWORD",
         3:  "NO AP FOUND",
         4:  "STAT_CONNECT_FAIL",
        }
     
     mySSID = 'Here_goes_your_SSID'
     myPass = 'Here_goes_your_Password'
     
     # Unbedingt das AP-Interface ausschalten
     nac=network.WLAN(network.AP_IF)
     nac.active(False)
     nac=None
     
     # Wir erzeugen eine Netzwerk-Interface-Instanz
     # Wir brauchen das STATION-Interface
     nic = network.WLAN(network.STA_IF)
     nic.active(False) # erst einmal zuruecksetzen
     
     # Abfrage der MAC-Adresse zum Eintragen im Router,
     # damit die Freigabe des Zugangs erfolgen kann
     MAC = nic.config('mac')  
     myID=hexMac(MAC)
     print("Client-ID",myID)
     
     # Wir aktivieren das Netzwerk-Interface
     nic.active(True)
     
     # Aufbau der Verbindung
     # Wir setzen eine statische IP-Adresse
     nic.ifconfig(("10.0.1.91","255.255.255.0","10.0.1.20","10.0.1.100"))
     
     # Anmelden am WLAN-Router
     nic.connect(mySSID, myPass)  
     
     if not nic.isconnected():
         # warten bis die Verbindung zum Accesspoint steht
         while not nic.isconnected():
             print("{}.".format(nic.status()),end='')
             sleep(1) # wir fragen im Sekunden- Rhythmus nach
         
     # Wenn verbunden, zeige Verbindungsstatus & Config-Daten
     print("\nVerbindungsstatus: ",connectStatus[nic.status()])
     if nic.isconnected():
         # War die Konfiguration erfolgreich? Kontrolle
         STAconf = nic.ifconfig()
         print("STA-IP:\t\t",STAconf[0],"\nSTA-NETMASK:\t",\
               STAconf[1],"\nSTA-GATEWAY:\t",STAconf[2] ,sep='')

    Eine weitere Zeile ist wichtig:

    nic.ifconfig(("10.0.1.91","255.255.255.0","10.0.1.20","10.0.1.100"))

    Die hier angegebenen Adressen müssen natürlich Ihrem lokalen Netz entsprechen. Angenommen, Ihr Router hat die IP 192.168.12.1/24 = 192.168.12.1 Net-Mask 255.255.255.0. Weiter angenommen, die IP 192.168.12.55 in Ihrem Netz ist noch frei, der Router ist gleichzeitig das Gateway ins Internet und macht auch die Namensauflösung (DNS). Dann müsste diese Zeile so aussehen:

    nic.ifconfig(("192.168.12.55","255.255.255.0","192.168.12.1","192.168.12.1"))

    Beachten Sie bitte die doppelten Klammern. Das äußere Paar gehört zum Funktionsaufruf, das innere fasst die 4 Strings zu einem Tuple zusammen, wie es die Methode ifconfig() verlangt.

    Nachdem diese Sequenz durchgelaufen ist, sollte sich im Terminalbereich eine Ausgabe der folgenden Art zeigen.

    >>> %Run -c $EDITOR_CONTENT
         Constructor of Arduino-Interface
         Arduino @ 0x24
         Client-ID ec-fa-bc-bc-47-c4
         #32 ets_task(4020ee60, 28, 3fff92d0, 10)
         1.1.1.
         Verbindungsstatus:  STAT_GOT_IP
         STA-IP: 10.0.1.91
         STA-NETMASK: 255.255.255.0
         STA-GATEWAY: 10.0.1.20

    Nach drei Sekunden war also die Verbindung hergestellt. Die IP-Adresse sollte immer dann fest eingestellt werden, wenn es sich bei dem Gerät um einen Server handelt. Sie wollen diesen sicher immer unter derselben URL erreichen und nicht jedes Mal raten müssen, welche IP der DNS des Routers dem Gerät verpasst hat.

    An dieser Stelle haben wir jetzt die Verbindung zum lokalen Netzwerk geschaffen. Es fehlt nur noch ein Werkzeug dazu, diesen Kanal auch zu nutzen. Wir können grundsätzlich aus einer ganzen Reihe von Diensten einen auswählen, TCP-Webserver, TCP-Client, UDP-Server, UDP-Client, MQTT, FTP, Telnet…

    Beginnen wir mit einem TCP-Webserver, der UDP-Server kommt danach dran.

    Der TCP-Webserver

    Dieser Server ist, ebenso wie das WLAN-Paket, eine Datei, die ich einfach in Anwendungen einbinden kann. Je nach Environment müssen Anpassungen vorgenommen werden, so auch hier. Weil keine RGB-LED direkt am ESP8266-01 angeschlossen werden kann, lösche ich alle Zeilen, die das voraussetzen würden. Letztlich macht das den Code schlanker und das ist beim ESP8266 nicht verkehrt.

    Ein Webserver muss eine Webpage an den Browser senden, welche entsprechende Steuerelemente enthalten muss, deren Daten wir zum Ändern der Einstellungen auf dem Nano V3 benötigen. In diesem Fall habe ich mich für drei Formulare mit getrennten Sende-Buttons entschieden. Es gibt also eine Menge HTML-Code, den wir uns zuerst anschauen.

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
    <HTML>
    <HEAD>
    <TITLE> Arduino-Steuerung </TITLE>
    <link rel="icon" href="data:;base64,=">
    </HEAD>

    <BODY>
    <H1> ARDUINO-RC </H1>

    <FORM METHOD=GET ACTION="http://10.0.1.91:9009">
    LED-Steuerung:
    <input type=text name=led size= 8 maxlength=6 value="120">
    <input type=submit value="FADE LED" name="LED">
    </FORM>

    <FORM METHOD=GET ACTION="http://10.0.1.91:9009">
    Schalte der RGB-LED:<BR>
    ROT <INPUT TYPE="checkbox" NAME="RED" XXX><BR>
    GR&Uuml;N <INPUT TYPE="checkbox" NAME="GREEN" XXX ><BR>
    BLAU <INPUT TYPE="checkbox" NAME="BLUE" XXX ><BR>
    <INPUT TYPE="submit" value="SET_RGB" name="RGB">
    </FORM>

    <FORM METHOD=GET ACTION="http://10.0.1.91:9009">
      Betriebsspannung  <INPUT TYPE="submit" value="GET_Ubat" name="UBAT"> ist: XXX V
    </FORM>

    </BODY>
    </HTML>

    In schwarz sehen Sie den Basisaufbau einer HTML-Seite, die sogenannten Tags. Dieser Text in den spitzen Klammern sind Formatierungsanweisungen für den Browser, der HTML-Dokumente in normalen formatierten Text mit Bildern und Links umsetzt. HTML ist das Akronym für Hyptertext Markup Language, also eine textbasierte, maschinenlesbare Sprache zur Formatierung und Gliederung von Texten. Die meisten Tags haben ein Öffnungs- und ein Schluss-Tag. Die Namen sind jeweils gleich, nur beim Schluss-Tag wird dem Namen ein "/" vorangestellt.

    Der minimale Rahmen für ein HTML-Dokument sieht also so aus:

    <HTML>
    <HEAD>

    </HEAD>
    <BODY>

    </BODY>
    </HTML> 

    Die einzelnen Formulare sind in verschiedenen Grüntönen dargestellt. Jedes beginnt mit der Festlegung einer Übertragungsmethode, hier ist es GET. ACTION legt die Zieladresse der Anfrage fest. Die input-Tags definieren bestimmte Formularelemente. Wir arbeiten mit einem Textfeld, 3 Checkboxen und den Submit-Buttons. Dazwischen hinein gestreut ist normaler Text außerhalb der Tags. Im Browser sieht das dann so aus – wenn's ganz fertig ist.

    Abbildung 4: Formular im Browser

    Abbildung 4: Formular im Browser

    Als aufmerksamer Leser sind Ihnen sicher die fünf rot markierten Stellen im HTML-Text aufgefallen. Dort müssen nämlich variable Inhalte eingefügt werden. Der Text muss also aus mehreren Blöcken zusammengesetzt werden. Dafür nutze ich drei Features von MicroPython.

    • Textkonstanten können in Hochkommata eingeschlossen werden ('Text') oder in Anführungszeichen ("Text").
    • Begrenzungszeichen im Text werden Bestandteil des Textes, wenn sie sich von den umgebenden unterscheiden.
      >>> a='Das ist eine "besondere" Eigenschaft'
      >>> print(a)
      Das ist eine "besondere" Eigenschaft
    • Textkonstanten, die über mehrere Zeilen reichen, werden in dreifache Begrenzungszeichen eingeschlossen.

    Im Seitentext kommen normale Anführungszeichen vor, deshalb habe ich die Textblöcke mit drei Hochkommata eingeschlossen. Das sieht dann im mittleren Abschnitt so aus.

    ….
    <FORM METHOD=GET ACTION="http://10.0.1.91:9009">
    Schalten der RGB-LED:<BR>
    ROT <INPUT TYPE="checkbox" NAME="RED"
    '''

    rgb_R='''
    ><BR>
    GR&Uuml;N <INPUT TYPE="checkbox" NAME="GREEN"
    '''

    rgb_G='''
    ><BR>
    BLAU <INPUT TYPE="checkbox" NAME="BLUE"
    '''
    ….

    Alle Teile sind in Variablen abgelegt, damit sie am Ende der Jobroutine webpage() mit den variablen Elementen zusammengesetzt werden können.

    def web_page(pos):
       global pwm,chkR,chkG,chkB,voltage
       if request.find("GET / HTTP")!=-1:
           pwm="?"
       if request.find("LED")!=-1:
           start=request.find("=")
           end=request.find("&")
           pwm=int(request[start+1:end])
           a.writeAnalog(3,pwm)
           pwm=str(pwm)
       if request.find("RGB")!=-1:
           if request.find("RED")!=-1:
               chkR=checked
               a.setBit(PORT,C,rt,1)
           else:
               chkR=""
               a.setBit(PORT,C,rt,0)
           if request.find("GREEN")!=-1:
               chkG=checked
               a.setBit(PORT,C,gn,1)
           else:
               chkG=""
               a.setBit(PORT,C,gn,0)
           if request.find("BLUE")!=-1:
               chkB=checked
               a.setBit(PORT,C,bl,1)
           else:
               chkB=""
               a.setBit(PORT,C,bl,0)
               
       if request.find("UBAT")!=-1:
           voltage=str(a.readAnalog(3,True,3))
       antwort=head+pwm+fading+chkR+rgb_R+chkG+rgb_G+chkB+rgb_B+voltage+ubat
       return antwort

    Wir beginnen mit der Deklaration der Variablen pwm,chkR,chkG,chkB,voltage als global, weil die Werte in der Funktion geändert werden sollen. Tun wir das nicht, gelten sie als lokale Variablen. Deren Inhalt wird nach Verlassen der Funktion vergessen und nicht nach draußen übergegeben.

    Die vier if-Konstrukte klopfen die ersten 50 Zeichen der Anfrage auf das Vorkommen bestimmter Textpassagen ab. Eine Anfrage, die im Browser mit 10.0.1.91:9009/ abgeschickt wird, enthält die Passage "GET / HTTP". Daraufhin sendet der Server eine nackte Startseite an den Browser (Abbildung 2) zurück. Wurden eine oder mehrere der Checkboxen aktiviert und der Button SET_RGB geklickt, dann übermittelt der Browser die folgende Anfrage.

    GET /?RED=on&GREEN=on&RGB=SET_RGB HTTP/1.1

    Im Query-String tauchen nur die Felder auf, die angehakt waren. Wir suchen also nach RED, GREEN und BLUE, setzen die chkX -Variablen auf checked oder den leeren String und schalten die entsprechende LED ein oder aus, indem wir den setBit-Befehl an den Nano V3 schicken.

    Findet sich ein UBAT im request-String, holen wir uns den Wert der Betriebsspannung des ESP8266-01 und belegen damit die Variable voltage. Danach wird der String für den kompletten Seitentext zusammengesetzt und zurückgegeben.

    Nach der Besprechung der Prerequisites (aka vorbereitende Maßnahmen) schauen wir mal, was der Hauptteil des Server-Codes so macht.

    led=Pin(2,Pin.OUT,)
    led.value(1)  # LED an Pin 2 folgt negativer Logik

    try:
     import usocket as socket
    except:
     import socket

    checked='checked="checked"'
    pwm="0"
    chkR=''
    chkB=''
    chkG=''
    voltage="???"

    Wir kapern uns die LED am ESP8266-01 zur Ausgabe, importieren das socket-Modul und belegen die globalen Variablen vor. Das dritte "checked" soll in Anführungszeichen erscheinen, also fassen wir den gesamten String in einfache Hochkommata ein.

    Dann bauen wir einen Socket als Empfangsportal für Anfragen auf. Die IP-Adresse haben wir bereits im WIFI-Teil eingerichtet: 10.0.1.91. Jetzt definieren wir eine Portnummer, 9009. Wir instanziieren das Socket-Objekt server aus der Adress-Familie IPv4 für TCP. Dem Socket wird mitgeteilt, dass die Adresse 10.0.1.91:9009 bei einem Neustart des Socket wiederholt zu verwenden ist. Der Timeout für die Empfangsschleife wird auf 0,1 Sekunden gesetzt und dann binden wir den Socket an die eingestellte Adresse, als IP wird durch die ' ' die bestehende verwendet. IP und Portnummer bilden wieder ein Tuple, deswegen die doppelten runden Klammern. Der Server soll auf genau eine eingehende Anfrage antworten.

    portNum=9009
    print("Fordere Server-Socket an")
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    server.settimeout(0.1)
    server.bind(('', portNum))    # an lokale IP und Portnummer 9009 binden
    server.listen(1)     Akzeptiere bis zu 1 eingehende Anfrage(n)
    print("Empfange Anfragen auf ",STAconf[0],":",portNum, sep='')

    Damit sind wir auch schon bei der Serverschleife angekommen.

    Abbildung 5: Client-Server-Prinzip

    Abbildung 5: Client-Server-Prinzip

    Die Empfangsschleife, die mit server.accept() aufgerufen wird, wird nach 0.1 Sekunden verlassen, wenn keine Anfrage eingetroffen ist. Es wird dann eine Exception geworfen, die wir mit try – except abfangen. Hätten wir keinen Timeout gesetzt (blockierender Socket), dann würde die Empfangsschleife alle nachfolgenden Aktionen blockieren, bis eine Anfrage eingeht. Das wäre für den Fall schlecht, wenn in der Endlosschleife noch weitere Aktionen zyklisch erfolgen sollen. Ist eine Nachricht eingetroffen, dann gibt server.accept() einen Kommunikationssocket c sowie die Socketadresse des Absenders der Nachricht zurück. Über den Kommunikationssocket c (braun) wird die weitere Unterhaltung abgewickelt.

    while 1:                      # Endlosschleife
     try:
       c, addr = server.accept()   # Anfrage entgegennehmen
       print('Got connection from {}:{}'.format(addr[0],addr[1]))
       led.value(0)
       # c ist ein bytes-Objekt und als string zu decodieren
       # damit string-Methoden darauf angewandt werden koennen.
       rawRequest=c.recv(1024)[0:50]
       request = rawRequest.decode("utf-8")
       print("*******Request**************XXX\n",request)
       getQuery=request.find("GET /?")
       getBlank=request.find("GET / ")
       if getQuery == 0 or getBlank == 0:
         print("**********GET-Position: {},{}\n\n".\
               format(getQuery,getBlank))
         response = web_page()
         #print("---------------\n",response,"\n-------------")
         c.send('HTTP/1.1 200 OK\n')
         c.send('Content-Type: text/html\n')
         c.sendall(response)
       else:
         print("#############\nQuery not valid\n#############")  
         response = web_page()
         c.send('HTTP/1.1 200 OK\n')
         c.send('Content-Type: text/html\n')
         c.sendall(response)
       sleep(0.1)
       c.close()
     except OSError:
       pass
     led.value(1)

    Aus dem Empfangspuffer lesen wir mit rawRequest=c.recv(1024)[0:50] die ersten 50 Zeichen. Das Bytes-Objekt wird mit request = rawRequest.decode("utf-8") in einen String verwandelt. Die print-Befehle sind nicht essentiell, unterrichten uns aber davon, was der Serverprozess so macht. Sie können auch weggelassen werden.

    Eine gültige Anfrage muss die Zeichen "GET / " oder "GET /?" enthalten und zwar ganz am Anfang bei Position 0. Ist das der Fall, dann geben wir den String an den Parser web_page() weiter. Seine Antwort geht an die Variable response. Wir bauen einen Header für die Seite ('http/1.1 200 OK\n' …) und schicken dann den gesamten Inhalt der Seite an den anfragenden Browser.

    Beginnt der empfangene Text aber mit etwas anderem, dann ist es keine gültige Anfrage in unserem Sinn. Damit sich der Browser zufriedengibt, schicken wir ihm die zuletzt aktuelle Seite einfach noch einmal, die Daten befinden sich ja noch in den Variablen. Die nervende Anfrage des Browsers nach einer Datei namens favicon.ico haben wir bereits mit der Zeile

    <link rel="icon" href="data:;base64,=">

    im Header unseres HTML-Codes geblockt.

    Nachdem die Kommunikation komplett abgewickelt ist, lassen wir dem Socket c noch ein paar Takte Zeit die Verbindung abzubauen und schließen dann den Socket. Als Exception gibt es nichts zu behandeln, wenn es nur ein Timeout war. Exceptions, die auf anderen Fehlern basieren, führen weiterhin zum Programmabbruch.

    Wenn Nano V3 und der ESP8266-01 einsatzbereit sind, laden wir das Modul arduino_i2c.py in den Flash hoch und öffnen die Datei arduino_goes_wlan_TCP.py in einem Editorfenster von Thonny. Nach dem Start mit F5 meldet sich der Server.

    >>> %Run -c $EDITOR_CONTENT
       Constructor of Arduino-Interface
       Arduino @ 0x24
       Client-ID ec-fa-bc-bc-47-c4
       #52 ets_task(4020ee60, 28, 3fff92d0, 10)
       1.1.1.
       Verbindungsstatus:  STAT_GOT_IP
       STA-IP: 10.0.1.91
       STA-NETMASK: 255.255.255.0
       STA-GATEWAY: 10.0.1.20
       Fordere Server-Socket an
       Empfange Anfragen auf 10.0.1.91:9009

    Wir starten die erste Anfrage mit der URL: http://10.0.1.91:9009. Über die Formularelemente können wir jetzt die Aktionen auf dem Nano V3 auslösen. Schulterklopfen! AVR went ESP!

    Der UDP-Server

    Der UDP-Server sendet keine Webseite zurück, sondern UDP-Pakete, die er aufgrund zuvor empfangener Nachrichten oder aus eigenem "Entschluss" schnürt und an UDP-Clients sendet.

    Der gesamte Teil des Programms, vom Einrichten des ARDUINO-Objekts bis zum Ende des WLAN-Zugangs, ist identisch mit dem unter WLAN-Zugang beschriebenen Vorgehen.

    Auch das Einrichten des Sockets unterscheidet sich nur wenig vom TCP-Bruder. Wir richten dieses Mal aber eine Abbruchtaste mit ein und instanziieren einen UDP-Socket.

    # udp_server.py
    # rudimentaerer UDP-Server nicht blockierend
    # eine WLAN-Verbindung wird vorausgesetzt
    # **********************************
    #from machine import Pin
    import socket

    taste=Pin(0,Pin.IN,Pin.PULL_UP)

    #
    # UDP-Server einrichten
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    s.bind(('', 9009))
    print("waiting on port 9009...")
    s.settimeout(0.1)

    Der Parser ist etwas schlanker, denn er braucht keinen HTML-Code zerlegen, sondern nur Befehls-Strings von folgender Struktur, die an die Klasse ARDUINO angepasst ist.

    command: parameter1;parameter2

    command

    Parameter1

    Parameter2

    FADE

    line

    value

    RGB

    (rgb)

     

    UBAT

    digits

     

    Der ankommende Request wird zuerst am ":" getrennt, um das Kommando abzutrennen. Die if-elif-Struktur prüft auf die gültigen Kommandos und leitet die Untersuchung der Parameter ein. Die geparsten Inhalte werden in geeigneter Form an die ARDUINO-Instanz a weitergegeben. Außerdem wird jeweils eine Rückantwort generiert. (rgb) besagt, dass eine beliebige Kombination der drei Zeichen in beliebiger Reihenfolge angegeben werden kann.

    def doit(request):
       reply="done"
       command,job=request.split(":")
       if command.upper()=="FADE":
           line,value=job.split(";")
           line=int(line); value=int(value)
           a.writeAnalog(line,value)
       elif command.upper()=="RGB": # gb
           a.writeIO(DDR,C,0b111)
           cols=0
           job=job.upper()
           if "B" in job:cols=1
           if "G" in job:cols+=2
           if "R" in job:cols+=4
           a.writeIO(PORT,C,cols)
           cols=a.readIO(PORT,C)[0] & 0b111
           reply="RGB set to {:03b}".format(cols)
       elif command.upper()=="UBAT":
           dig=int(job)
           value=a.readAnalog(3,digits=int(dig))
           reply="ESP86-01 U0 = {}".format(value)
       return reply

    Die Serverschleife ist ebenfalls sehr einfach gestrickt. Auch hier wird die Timeout-Exception durch try – except abgefangen.

    Bei UDP gibt es keinen Kommunikationssocket wie bei TCP, denn UDP arbeitet als ungesichertes Verbindungsprotokoll ohne Handshake und Kontrollen. Dafür ist das Protokoll schneller und unkompliziert, vergleichbar mit RS232. Wenn die Funktion recvfrom() den Eingang von Zeichen feststellt, werden die Zeichen und die Adresse des Absenders als Tuple zurückgegeben. Das Tuple entpacken wir, wie bei TCP, in das Bytesobjekt rec und die Socket-Adresse adr. Das Bytesobjekt wird zum String dekodiert und, sofern vorhanden, von störenden Begrenzern, \r = Wagenrücklauf und \n Zeilenvorschub befreit.

    Dann geben wir den String an doit() zum Parsen und Ausführen der Befehle. Die Antwort codieren wir wieder als Bytes-Objekt und senden es an den Auftraggeber zurück. Mit weiteren sendto()-Befehlen könnten wir dieselbe Nachricht auch noch an weitere Empfänger senden. Das ist manchmal zu Debugging-Zwecken recht nützlich.

    # Serverschleife
    while 1:
       gc.collect()
       try:
           # Nachricht empfangen
           rec,adr=s.recvfrom(150)
           rec=rec.decode().strip("\r\n")
           print(rec,adr)
           # Nachricht parsen und
           # Aktionen ausloesen
           answer=doit(rec)
           # Ergebnisse encodiert oder als String senden
           # es kann an mehrere Adressen gesendet werden
           reply=answer.encode()
           s.sendto(reply,adr)
           rec=""        
       except OSError:
           pass # timeout uebergehen
       if taste.value()==0:
           sys.exit()

    Die letzten beiden Zeilen ermöglichen einen sauberen Ausstieg aus der Schleife ohne Strg+C, wenn die Flash-Taste gedrückt wird.

    Nano V3 und ESP8266-01 sind wohl vom vorigen Versuch noch aufgebaut. Dann laden wir das Programm arduino_goes_wlan_UDP.py in ein Editorfenster, das Modul arduino_i2c.py befindet sich ja auch bereits im Flash des ESP8266-01. Na dann, Start mit F5.

    Ja – und wie soll ich das jetzt testen? Fragen Sie sich wahrscheinlich, denn mit dem Browser geht das nicht. Gut, aber es gibt ein tolles Werkzeug, das sowohl TCP als auch UDP als Server und Client (und noch mehr) beherrscht. Laden Sie am besten die Portable-Version des Programms "Packet Sender" herunter und entpacken Sie es in einem Verzeichnis Ihrer Wahl. Es wird ein Verzeichnis "PacketSenderPortable" angelegt. Starten Sie dann die darin enthaltene Datei packetsender.exe.

    Bevor Sie beginnen, müssen Sie eine eigene Portnummer für Ihren PC vergeben.

    Abbildung 6: Packetsender Settings

    Abbildung 6: Packetsender Settings

    Abbildung 7: Portnummer für UDP vergeben

    Abbildung 7: Portnummer für UDP vergeben

    Dann bereiten wir die Sendung des ersten UDP-Befehls vor.

    Abbildung 8: Der erste UDP-Befehl

    Abbildung 8: Der erste UDP-Befehl

    1. Der Name ist nur wichtig, falls Sie den Befehl speichern wollen.
    2. Hier Geben wir den Befehl für den ESP8266-01ein.
    3. Die Adresse des Ziels
    4. Die Portadresse des Ziels
    5. Das Übertragungsprotokoll ist UDP
    6. Und ab geht die Post!

    In den unteren beiden Zeilen wird unser Befehl und die Antwort vom ESP8266-01 detailliert aufgelistet. Jetzt können Sie weitere Befehle testen. Ein Bonbon wartet aber noch auf Sie.

    Die UDP-Android-App

    Jetzt wäre es natürlich schön, wenn der Nano V3 nicht nur über den PC steuerbar wäre. Wie wäre es mit dem Handy? Der MIT-AppInventor2 ist ein feines Werkzeug, mit dem auch Leute, die keine Ahnung vom Programmieren auf dem Android-System haben, ganz gut und einfach Programme, Verzeihung, wollte sagen Apps, entwickeln können. Haben Sie früher gerne mit Bauklötzen gespielt, Holz oder Lego? Dann kommen Sie mit dem AppInventor2 auch zurecht, denn es passt nur zusammen, was zusammengehört. Eine Anleitung für einen Einstieg finden Sie hier.

    Das Tool wird nicht auf dem PC installiert, Sie arbeiten ganz einfach über Ihren Browser. Auf dem Handy wird aus dem Google Playstore die App MIT AI2 Companion geladen und installiert. So können Sie nach dem Aufbau einer Verbindung mit Ihrem PC auf diesem eigene Apps entwickeln und 1:1 sehen, wie das auf Ihrem Handy ausschaut. Ebenso zeitgleich lassen sich die programmierten Funktionen und Abläufe testen.

    Abbildung 9: Zum AI-Companion verbinden

    Abbildung 9: Zum AI-Companion verbinden

    Wenn alles wunschgemäß läuft, erstellen Sie eine apk-Datei, die Sie auf dem Handy permanent installieren können. So sieht der Bildschirm der App aus, die ich für unser Projekt gebaut habe:

      Abbildung 10: Screenshot von der App:

    Abbildung 10: Screenshot von der App:

    Und das ist der Entwurf im AppInventor2-Fenster:

    Abbildung 11: Entwurf der App

    Abbildung 11: Entwurf der App

    Und das ist ein (kleines) Stück des Bauplans:

    Abbildung 12: Ein Teil des Bauplans

    Abbildung 12: Ein Teil des Bauplans

    Für Neugierige gibt es zum Applet eine aia-Datei. Die können Sie im MIT-AI2 über das Projects-Menü nach Belieben importieren, ändern und ergänzen. Lassen Sie sich nicht von der Vielfalt der Möglichkeiten verwirren. Probieren Sie am Anfang kleine, überschaubare Apps zu erstellen. Für jeden Block kann man über einen Rechtsklick Hilfe anfordern. Wenn Sie die Liste meiner bislang veröffentlichten Blogposts durchgehen, werden Sie weitere Projekte finden, zu denen es eine App gibt (zum Beispiel Robot Car, Temperaturwächter, Mückenschreck), die Sie als Beispiel nehmen können.

    Für Eilige habe ich auch etwas, die fertige apk-Datei, die Sie zum Beispiel via Bluetooth aufs Handy verfrachten können. Dort starten Sie die Datei zur Installation. Wie das im Einzelnen geht, steht auch in der Anleitung im letzten Abschnitt.

    Damit die Steuerung funktioniert, müssen Sie natürlich die IP-Adressen an Ihr Netzwerk anpassen. Vergleichen Sie dazu das Kapitel "Der WLAN-Zugang". Und noch eins, die UDP-Erweiterung von Ullis Roboterseite hat das gleiche Problem mit der IP-Adresse wie MicroPython. Nur bei MicroPython kann man das Problem durch die Zeile

    s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

    lösen, in der Handy-App nicht. Das Problem tritt auf, wenn man bei laufender App das Listener-Objekt neu starten muss, etwa nach einem Netzwerkausfall. Dann gibt es die Fehlermeldung 2, und die besagt, dass die bereits verwendete Adresse (eigentlich die Socketadresse) nicht ein zweites Mal vergeben werden kann. Schuld ist dabei nicht die IP-Adresse, sondern die Portnummer. Also App schließen und neu starten.

    Ein weiteres Problem ist erneut die Socketadresse des Listeners und wieder ist der Schuldige die Portnummer. Das Handy würfelt sich eine Portnummer beim Versenden eines Pakets zufällig aus. Der ESP8266-01 empfängt diese Nummer korrekt und versucht seinerseits das Ergebnis an eben diesen Socket zurückzusenden. Das Senden funktioniert auch, nur hat der UDP-Listener im Handy keine Ahnung davon, dass der eigene Transmitter diese Portnummer vergeben hat und dass infolge der Listener, das Paket in Empfang nehmen sollte. Da warten wir ewig auf die Antwort vom ESP8266-01.

    Beim Packet Sender tritt das Problem nicht auf, denn der versendet Pakete stets mit der für Sender und Empfänger identischen Portnummer, die wir im Programm eingestellt haben.

    Abbildung 13: Das Handy sagt: falsche Hausnummer

    Abbildung 13: Das Handy sagt: falsche Hausnummer

    Also vergeben wir sowohl beim ESP8266-01 als auch in der Handy-App eine feste Portnummer.

    Abbildung 14: Jetzt stimmt die Hausnummer

    Abbildung 14: Jetzt stimmt die Hausnummer

    In der Serverschleife sieht das dann so aus.

    # Serverschleife
    while 1:
       gc.collect()
       try:
           # Nachricht empfangen
           rec,adr=s.recvfrom(150)
           rec=rec.decode().strip("\r\n")
           print(rec,adr)
           remoteIP,_= adr
           # Nachricht parsen und
           # Aktionen ausloesen
           answer=doit(rec)
           # Ergebnisse encodiert oder als String senden
           # es kann an mehrere Adressen gesendet werden
           reply=answer.encode()
           s.sendto(reply,(remoteIP,9091))
           rec=""        
       except OSError:
           pass # timeout uebergehen
       if taste.value()==0:
           sys.exit()

    In den beiden Dateien zur Handy-App arduino_rc.aia und arduino_rc.apk ist die feste Portnummer bereits berücksichtigt, in der Datei arduino_goes_wlan_UDP.py ebenso. Nur wenn Sie den Programmtext selbst eingegeben haben, müssen Sie die Zeilen noch einfügen, respektive ändern.

    Abbildung 15: Feste Portnummer beim ESP8266

    Abbildung 15: Feste Portnummer beim ESP8266

    In der Handy-App kann man die IP-Adressen und Portnummern bei laufender App ändern. Mit dem Ziffernblock und einer Eingabefunktion wäre die Änderung der Portnummer auch auf dem ESP8266-01 möglich. Mit dieser Anregung schließt sich der Themenkreis dieser Blogfolge.

    Viel Vergnügen mit der WLAN - ESP8266-01 – AVR – Brücke und viel Erfolg bei der Erkundung des MIT-AppInventor2.

    Esp-8266Für arduinoProjekte für fortgeschritteneSmart home

    Deja un comentario

    Todos los comentarios son moderados antes de ser publicados