Tischlampe mit Touchsteuerung am ESP32 - Teil 1 - AZ-Delivery
Dieser Beitrag ist auch als PDF-Dokument verfügbar.

Die Engellampe ist nett für die Weihnachtszeit, aber zum einen nicht interaktiv und für den Rest des Jahres nicht gerade stilgerecht und ebenso deplatziert, wie die neuen Lebkuchen im August oder die Osterhasen kurz nach Neujahr. Daher wollen wir heute und in den folgenden Beiträgen überlegen, wie wir aus der Engellampe ein Beleuchtungsobjekt erzeugen können, dessen Leuchteffekte direkt am Gerät gesteuert werden können.

Vorigen Sommer habe ich, angeregt durch einen Besuch bei einem befreundeten Ehepaar, eine LED-Lampe für lauschige Sommerabende gebaut und programmiert. Das Teil habe ich schließlich mit einer App vom Android-Handy gesteuert. Innovativ war bei mir der Einsatz eines Neopixelrings, wodurch entgegen des vorgefundenen Originals die Lichtfarbe verändert werden konnte.

Beim Besuch eines Lokals kam mir nun vor ein paar Wochen die Variante einer solchen Tischleuchte buchstäblich unter die Finger. Das Ding war durch Berührung zu steuern, allerdings in etwas mystischer Art und Weise. Anlass genug, um mit dem Einsatz eines ESP32 weiter zu forschen. Und – es ist gelungen, natürlich unter Verwendung von MicroPython. Hier ist das Ergebnis in einer weiteren Folge aus der Reihe

MicroPython auf dem ESP32 und ESP8266

heute

Eine Tischlampe mit Touch-Steuerung

Mit der Handy-App hat man zur Steuerung einer Tischlampe natürlich viele Möglichkeiten. Aber die Probs beginnen schon mit der Entscheidung - WLAN über Router, oder über den eigenen Accesspoint des Controllers. Das muss im Programm berücksichtigt werden, oder beim Start bereits zur Auswahl stehen.

Schön wäre es, wenn die Lichtabgabe interaktiv direkt am Gerät gesteuert werden könnte. Genauso ein Gerät werden wir jetzt bauen und programmieren. Der Controller der ersten Wahl war ein ESP32, denn der hat Touch-Pad-Eingänge. Weitere Überlegungen zur Verwendung eines ESP8266 oder der Raspberry Pi Pico brachten dann weitere Lösungen des Touch-Problems hervor. Mit diesen werden wir uns in den nächsten Folgen beschäftigen. Heute bleiben wir beim ESP32. Ich werde zeigen, wie man Daten dauerhaft im NVS-Bereich ablegen kann und wie man erreicht, dass Prozesse parallel ausgeführt werden. Zumindest das erste Feature bietet nur der ESP32.

Die Hardware

Ein ESP32, eine RGB-LED, ein Neopixel-Ring und, für den Anfang, drei Platinenstücke, stellen die Hardware für dieses Projekt dar. Daneben brauchen wir natürlich die entsprechende mechanische Basis, damit aus der reinen Elektronik ein einsetzbares Gerät wird, dazu kommen wir später.

1 ESP32 Dev Kit C unverlötet oder ESP32 Dev Kit C V4 unverlötet oder ESP32 NodeMCU Module WLAN WiFi Development Board mit CP2102 oder NodeMCU-ESP-32S-Kit oder ESP32 Lolin LOLIN32 WiFi Bluetooth Dev Kit
1 RGB LED Ring 8 bit WS2812 5050 + integrierter Treiber
1 KY-016 FZ0455 3-Farben RGB LED Modul 3 Color oder LED Leuchtdioden Sortiment Kit, 350 Stück, 3mm & 5mm, 5 Farben - 1x Set
1 Breadboard Kit - 3x Jumper Wire m2m/f2m/f2f + 3er Set MB102 Breadbord
1 Widerstand 560 Ω
2 Widerstand 10kΩ
optional Logic Analyzer

Für das verwendete Controllerboard ESP32 Dev Kit C V4 braucht man für die Entwicklung der Schaltung zwei Breadboards, die über eine Stromschiene seitlich zusammengesteckt werden. Nur so bekommt man genug freie Kontaktstellen für die Jumperkabel.

Abbildung 1: Aufbau mit ESP32

Abbildung 1: Aufbau mit ESP32

Als Touchpads habe ich Reststücke einer Platine und eine Krokse benutzt. Im Produktionssystem werden dafür kleine Aluzylinder dienen.

Die RGB-LED verwende ich zum Anzeigen des Betriebszustands. Die Farben der LEDs am Ring können einzeln rauf- und runtergeregelt werden. Welche Farbe grade dran ist, zeigt die RGB-LED an. Insgesamt wird es fünf Zustände geben: rot, grün, blau, weiß und ein-aus.

Die Vorwiderstände sind so dimensioniert, dass zusammen etwa weißes Licht herauskommt. Die blaue und vor allem die grüne LED sind um vieles heller als die rote. Daher rühren die großen Unterschiede in den Widerstandswerten. Wer eine hellere Anzeige möchte, nimmt einfach kleinere Ohmwerte.

Wie alles zusammengehört, das zeigt das Schaltbild in Abbildung 2.

Abbildung 2: Schaltung mit ESP32 und Touchpads

Abbildung 2: Schaltung mit ESP32 und Touchpads

Als Energieversorgung habe ich eine Li-Ion-Zelle vom Typ 18650 gewählt, dazu einen Batteriehalter mit Ladeteil und 5V Ausgang. An die Anschlusspins der USB-A-Buchse habe ich die Versorgungsleitungen für meine Schaltung gelötet. Auf diese Weise komme ich ohne USB-Stecker aus und kann trotzdem den mechanischen Schalter an der Platine nutzen. Er wird im Gehäuse in Richtung Boden zeigen, ebenso wie die Ladebuchse am oberen Ende der Platine in Abbildung 2.

Weil in der Lampe für die Breadboards zu wenig Platz sein wird, habe ich eine Platine entworfen, auf der die wenigen Einzelteile Platz finden. Sie können das Layout als PDF-Datei herunterladen.

Abbildung 3: Sensorlampe - Layout

Abbildung 3: Sensorlampe - Layout

Im Versuchsaufbau habe ich ein RGB-LED-Modul verwendet. Für die Lampe selbst fand ich eine der LEDs aus dem Sortiment optimaler wegen der einfacheren Montage im Deckel. Die LED wird einfach mit Zweikomponentenkleber in die Fassung geklebt.

Abbildung 4: RGB-LED mit gemeinsamer Kathode

Abbildung 4: RGB-LED mit gemeinsamer Kathode

Die Lampenteile

Gehäuserahmen und Deckel sind aus Holz gefertigt. Der Rahmen besteht aus 15mm Ahornbrettchen 45mm x 150mm. Als Deckel habe ich eine 5mm starke Platte genommen, an die mittig ein Holzzylinder für die Aufnahme des Messingrohrs geklebt wurde.

Abbildung 5: Lampenteile

Abbildung 5: Lampenteile

Abbildung 6: Rahmenleisten vor dem Verkleben

Abbildung 6: Rahmenleisten vor dem Verkleben

Abbildung 7: Einbau in den Gehäuserahmen und Lampenhalterung im Deckel

Abbildung 7: Einbau in den Gehäuserahmen und Lampenhalterung im Deckel

Abbildung 8: Sensortasten und Signal-LED

Abbildung 8: Sensortasten und Signal-LED

Der Lampenkopf sitzt auf einem 230mm langen Messingrohr mit 6mm Ø.

Abbildung 9: LED-Ring im Lampenkopf

Abbildung 9: LED-Ring im Lampenkopf

Die Software

Fürs Flashen und die Programmierung des ESP32:

Thonny oder

µPyCraft

Signalverfolgung:

Saleae Logic 2

Verwendete Firmware für einen ESP32:

MicropythonFirmware Download

v1.19.1 (2022-06-18) .bin

Die MicroPython-Programme zum Projekt:

timeout.py Nichtblockierender Software-Timer

touch.py Touchpad-Modul

touchlampy.py Betriebsprogramm

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 Micropython-Firmware (Stand 18.06.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.

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 main.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.

Der Neopixelring

Neopixel-LEDs vom Typ WS2812 enthalten drei einzelne LEDs, die rotes, grünes oder blaues Licht abgeben. Angesprochen werden sie von einem Controller, der seine Anweisungen über eine Art Bussystem erhält, das mit 800kHz getaktet wird.

Beim I2C-Bus oder beim SPI-Bus erreichen die Signale vom Controller, zum Beispiel einem ESP32, alle Slaves am Bus in gleicher Weise, alle sehen alles. Bei den WS2812-Bausteinen ist das anders. Jeder Baustein hat einen Dateneingang und einen Datenausgang. Mehrere Bausteine können kaskadiert werden, indem man den Dateneingang jedes weiteren Bausteins an den Datenausgang des Vorgängers anschließt. Der erste Baustein in der Kette bekommt vom ESP32 eine Pulskette von drei Bytes für R, G und B zugespielt, die er selbst futtert, also vom gesamten Pulszug entfernt. Alle nachfolgenden Impulse werden durchgewunken und am Datenausgang abgegeben. Jeder Baustein in der Kette holt sich auf dieselbe Weise seinen Anteil und gibt den Rest weiter. So wird es möglich, dass jeder Baustein in der Kette individuell angesteuert werden kann. Die Intensität jeder Farbe kann in 256 Stufen variiert werden, je nach dem Wert des empfangenen Bytes für die einzelne Farbe. Wir geben den Farbcode in Form einer Liste mit drei Bytes für jeden WS2812 an.

Starten wir mit einem Versuch. Wir verwenden den Aufbau von Abbildung 2. Zuerst werden die Klassen Pin und NeoPixel importiert. Ich erzeuge ein GPIO-Pin-Objekt als Ausgang und instanziiere damit ein Neopixel-Objekt mit acht Bausteinen.

Die NeoPixel-Instanz neo enthält ein Bytearray buf und die Methode write(), mit welcher der Inhalt des Bytearrays zum Neopixelring übertragen wird.

Mit neo[0] spreche ich die ersten drei Elemente des Arrays an und übergebe die Werte für rot, grün und blau, 0xe0, 0x07 und 0x3c. Intern macht das die private Funktion setitem(). In den Buffer werden die Werte in veränderter Reihenfolge eingetragen. Wie wir gleich am Kurvenzug sehen werden, den ich mit Hilfe des Logic Analyzers aufgezeichnet habe. Die Werte werden so gesendet, wie sie im Puffer stehen. Einer 0 entspricht ein schmaler Impuls von ca. 500ns Breite gefolgt von einer Pause von ca. 750ns. Die 1 wird durch einen Puls von 750ns und einer Pause von 500ns gesendet. Es fällt auf, dass die ersten beiden Bytes in vertauschter Reihenfolge gesendet werden. Das entspricht der Information aus dem Datenblatt und kann auch wie folgt verifiziert werden.

Abbildung 10: Pulsfolge für RGB = 0xE0, 0x07, 0x3C, 0xf0, 0xf0, 0xf0

Abbildung 10: Pulsfolge für RGB = 0xE0, 0x07, 0x3C, 0xf0, 0xf0, 0xf0

Am Ausgang des ersten WS2812B fehlen die drei Bytes 0xe0, 0x07 und 0x3c, die dieser Baustein gefressen hat. Stattdessen kommt der Code 0xf0, 0xf0, 0xf0 zeitversetzt heraus, der Code für den zweiten Baustein. Der Eingang für Kanal 1 des Logic Analyzers war für diese Messung am Eingang der ersten LED, Kanal 2 am Ausgang derselben angeschlossen.

Jetzt wissen wir, wie das Känguru beim WS2812B läuft und können uns dem Programm für die Lampe zuwenden.

Das Programm

Wir wollen die Neo-LEDs mit drei Sensor-Pads steuern. Mit dem Touchpad an GPIO33 wird die Farbwahl durchgeführt. Es gibt fünf Kanäle: An/Aus, rot, grün, blau und hell/dunkel. An GPIO32 wird die Intensität erhöht, an GPIO27 verringert. Die Bedienung dieser drei Aktivitäten läuft im Hintergrund parallel in drei Funktionen ab. Möglich wird das durch das Modul asyncio.

Zuerst hatte ich versucht, dafür das Modul _thread zu verwenden. Das hat für zwei Prozesse gut funktioniert, für den dritten aber den Dienst verweigert. Was die Ursache dafür war, konnte ich nicht entdecken. Also arbeiten wir mit asyncio.

Wir starten mit dem Importgeschäft. Das Modul touch unterstützt uns mit den Methoden zu den Touchpads. Pin brauchen wir für die LED-Ausgänge. Über NeoPixel verwalten wir die WS2812B des LED-Rings. Der Alias NP erlaubt uns eine kürzere Schreibweise. Für kleine Nickerchen dienen die Methoden sleep und sleep_ms aus dem Modul time. uasyncio ist die auf MicroPython angepasste Version asyncio aus C-Python, auch diesem Modul verpassen wir einen Alias. Dass die Klasse NVS nur beim ESP32 verfügbar ist, ist schon am Modul esp32 zu ersehen, von wo wir die Klasse importieren. NVS ist das Akronym für Non Volatile Storage. Damit können wir Daten dauerhaft im System speichern. Wir nutzen das für das Ablegen unserer Lieblingsfarbkombination, die dann beim Kaltstart automatisch wieder geladen wird. (Download: timeout.py touch.py touchlampy.py)

Der NVS-Bereich wird in Namensräume (name spaces) partitioniert, in denen vorzeichenbehaftete 32-Bit-Ganzzahlen und sogenannte binary blobs, also bytes-Objekte abgelegt werden können. Die Speicherstellen werden durch einen String als Schlüssel angesprochen. Unsere Partition trägt den Namen config.

Hier legen wir die Touchpad-Objekte fest. Das erste Argument ist die GPIO-Pinnummer. GPIO33 steht also für Touch8, GPIO32 für Touch9 und GPIO27 für Touch7. Das zweite Argument ist ein Grenzwert, ab dem das Touchpad als berührt eingestuft wird. Bei Berührung sinkt der eingelesene Wert und die Methode touched() liefert eine 1 zurück, sonst eine 0.

Zum Einrichten des Grenzwerts fragen wir den entsprechenden Eingang des TP-Objekts ohne Berührung direkt ab.

Dann mit Berührung.

Die Signalleitung des LED-Rings schließen wir an GPIO13 an. Der Ring hat 8 LEDs, genauso gut können Sie einen Ring mit 12 LEDs einsetzen. Der saugt natürlich dann auch den Akku schneller leer. Mit dem 8-er-Ring liegt die Stromaufnahme zwischen 40mA und 250 mA. Beim Einsatz einer 2200mAh-Zelle reicht eine Ladung also für ca. acht Stunden aufwärts, je nach eingestellter Helligkeit.

Die Bedeutung der 5 Kanäle habe ich oben bereits aufgeführt. Die Farbe in color deklarieren wir als Liste mit dem Code "alle LEDs aus" = [0,0,0].

Es folgen die Definitionen der Routinen zum Speichern und Auslesen der Farbcodes im NVS-Bereich. Als Schlüssel dienen die Farbnamen, die Werte kommen aus den Listenelementen. Wichtig: Erst mit dem Kommando commit() werden die Werte in den Speicher übertragen.

Das Auslesen geht einfacher. Die Farbwerte geben wir in Form einer Liste zurück. Würden wir die eckigen Klammern weglassen, bekämen wir als Rückgabeobjekt ein Tupel. Das können wir nicht brauchen, denn wir möchten ja im Programm die Elementwerte verändern, und das geht bei einem Tupel nicht.

Beim ersten Start ist der Namespace config noch leer. Ein Lesezugriff wirft daher eine Exception, die wir mit try-except abfangen. Statt den Farbcode einzulesen, schreiben wir den oben definierten Code in den Speicher. Beim nächsten Start existieren jetzt die Schlüssel red, green und blue.

Wir fahren mit der Definition weiterer Variablen und Objekte fort. merken ist ein flüchtiger Zwischenspeicher für den aktuellen Farbcode, wenn wir im laufenden Betrieb die LEDs dunkel schalten. gemerkt belegen wir dann mit True.

Dann definieren wir die Ausgänge für die RGB-LED. Die Objekte fassen wir im Tupel leds zusammen. Weil die Objektreferenzen nicht verändert werden, kann getrost ein Tupel verwendet werden. Auch für shapes kann ein Tupel genommen werden. Die Elemente sind wiederum Tupel und legen fest, wie Leitungen zur Signal-LED zu schalten sind. In channels stehen die Namen für die vier Kanäle, die wir für die Ausgabe in REPL brauchen.

Die Funktion lum() füllt den NeoPixel-Buffer mit dem Farbcode und sendet den Bufferinhalt über den GPIO13-Ausgang an den Ring.

Jetzt kommt das Modul asyncio ins Spiel. Wir definieren damit drei Funktionen, welche die Touch-Steuerung übernehmen. Mit tp1 schalten wir die Kanäle durch, speichern die aktuelle Farbe in NVS.config und schalten den Ring dunkel. Was jeweils geschehen soll, decodieren wir über die Zeitdauer, mit der das Pad berührt wird.

Eine Funktion, die einen Prozess im asyncio-System darstellen soll, muss mit async def eingeleitet werden. Der Prozess wird beendet, sobald die Funktion verlassen wird. Das wollen wir nicht, daher läuft der Prozess als Endlosschleife. Die Objekte kanal und color erfahren potenzielle Änderungen, die wir andernorts einsetzen müssen, deshalb deklarieren wir sie beim Start der Funktion als global.

Das normale sleep_ms() aus time ersetzen wir durch die asyncio-Variante asyncio.sleep_ms(). Mit der Zeile

signalisieren wir dem System, dass der Prozess an dieser Stelle unterbrochen werden darf, um andere Prozesse zu bedienen.

Die Schleife läuft leer durch, solange das Pad tp1 nicht berührt wird, touched() liefert dann eine 0 ab. Wird eine Berührung festgestellt, liefert touched() eine 1, und wir bestimmen mit getDuration() die Dauer in Millisekunden.

Ein kurzes Antippen bringt eine Zeitdauer von deutlich unter 100.

Ist die Haltezeit kleiner als 500ms, dann zählen wir den Kanal hoch und schalten die Signal RGB auf die entsprechende Farbe. Mit der Bildung des Teilungsrests Modulo 5 auf die Summe, begrenzen wir den Wertebereich auf 0…4 und erhalten so einen Ringzähler.

Liegt die Haltedauer zwischen einer halben bis zu zwei Sekunden, lösen wir die Speicherung der aktuellen Farbe aus.

Mehr als zwei Sekunden Haltedauer schaltet den LED-Ring dunkel.

Die Funktion showChannel() bedient die Ausgänge für die Signal-LED. Die Kanalnummer wird übergeben und auf Einhaltung des gültigen Bereichs getestet. assert wirft eine Exception, wenn das nicht der Fall ist. leds(i) spricht nacheinander die drei Ausgänge für rot, grün und blau an. Über num wird das entsprechende Muster abgerufen, und i adressiert die Werte im Tupel des Musters.

showChannel(3) ruft das Muster (0,0,1) für Kanal 3 (blau) auf und setzt ledR=0, ledG=0 und ledB=1.

Funktionen, die mit async def eingeleitet werden, verhalten sich kooperativ, sie lassen zu, dass mehrere Prozesse parallel zueinander ausgeführt werden können und werden deshalb als Coroutinen bezeichnet. Mit der Coroutine increase() zählen wir die Farbwerte des eingestellten Kanals hoch. Änderungen an color, merken und gemerkt müssen außerhalb des Prozesses sichtbar, also global sein. Der Prozess wird wieder in einer while-Schleife dauerhaft am Laufen gehalten und darf unterbrochen werden.

Bei Kanal 1 bis 3 wird jeweils eine diskrete Farbe gesteuert. Mit n steuern wir, welcher Wert beim Erhöhen des Farbwerts addiert wird. Wird das Pad gehalten, dann wird bis zum 9. Durchlauf der inneren while-Schleife eine 1 addiert, ab dem 10. Durchlauf eine 5. Um unerwünschte Seiteneffekte zu vermeiden, limitieren wir den Farbwert beim Erhöhen auf maximal 255. Den aktuellen Wert weisen wir dem entsprechenden Element in der Liste color zu. Als Index dient die um 1 verringerte Kanalnummer in ptr.

Bei Kanal 4 werden alle Komponenten von color um denselben Wert 3 erhöht. Das endet schließlich irgendwann bei [255,255,255]. Beim Zurückfahren wird dann lediglich die Intensität des weißen Lichts verringert. Die ursprünglich eingestellte Farbe wird dann nicht mehr erreicht. Das ist im Moment nicht sehr schön soll aber in einer weiteren Folge mit Sensorlampy2.0 optimiert werden.

Bei Kanal 0 ist die Signal-LED dunkel. Mit dem Decrease-Tab wird der aktuelle Farbecode in merken zwischengelagert und der Code [0,0,0] gesetzt. Hier, bei increase() wird umgekehrt der Farbcode restauriert und an den Ring gesandt. Das Flag gemerkt setzen wir zurück.

Die Funktion decrease() arbeitet analog, statt zu addieren, subtrahieren wir bei den Farbwerten.

Das Hauptprogramm ist auch als Coroutine verfasst. Zuerst erzeugen wir eine Event-Loop. Sie übernimmt die Steuerung der Prozesse in den Tasks. Beim Erzeugen derselben übergeben wir als Parameter die Funktionen und erklären, dass die Event-Loop ein Dauerläufer sein soll.

Wir schalten das Licht ein und starten die Funktion main(). Danach geht alles wie von selbst, alleine durch Berühren der Touchpads.

Abbildung 11: Die fertige Lampe

Abbildung 11: Die fertige Lampe

Wie geht es weiter? Als nächste Ausbaustufe werden wir das Programm für den ESP32 um einen programmgesteuerten Farbverlauf erweitern. Dann planen wir einen Verlauf, der durch Zufallszahlen Farbmischungen erzeugt. Auch in diesem Fall soll uns asyncio unterstützen. Schließlich haben wir noch das Ziel, eine bestehende Farbmischung unter Beibehaltung der Spektralanteile in der Helligkeit zu variieren.

Bis dann, bleiben Sie dran!

Esp-32Projekte für anfänger

Deja un comentario

Todos los comentarios son moderados antes de ser publicados