Restriction d'accès aux appareils via la carte RFID partie 3

Limite d'accès aux unités par contactless Card avec NodeMCU et le module RC522 module tiers-WPA, une interface Web avec une protection par mot de passe, des informations d'état et une barre de menus extensible.

Après avoir été au première partie si vous avez défini le matériel et que vous pouvez utiliser une carte pour activer le relais, nous pouvons utiliser notre ESP dans la troisième partie de cette série, un WPS Push Button pour configurer automatiquement la configuration sans fil, et une page de connexion à notre ESP afin de protéger la configuration contre les modifications non autorisées.


Connexion WPA:

Pour pouvoir continuer à stocker les données WPA Push Button dans notre Push, nous devons développer nos services de base dans la partie 1.

Nous avons besoin de 3 résistances précises avec 6,8 Kohm et un palpier. Ce palpeur n'inclut pas la révolte moyenne du diviseur de tension afin que notre ESP puisse enregistrer la pression sur les touches. L'image de la figure montre le débranchement nécessaire et doit devenir le circuit du premier Aritkel AJOUTER.

Voici le code: 

#include <SPI.h>
#include <MFRC522.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <EEPROM.h>

#define RST_PIN     5     // SPI Reset Pin (D1 sortant)
#define RELAIS-RELAIS  16    // Relais (D0 Sortie) [LOW Actif]-Le LED interne est également proche du port USB
#define SS_PIN      15    // SPI esclave Select Pin

#define RGBLED_R    2     // Rouge (D4 Sortie) 
#define RGBLED_G    0     // vert (D3 sorties)-LED interne sur le module ESP
#define RGBLED_B    4     // bleu (D2 Sortie)

#define WiFiPwdLen   25   // Longueur de mot de passe WiFi maximale
#define STANameLen 20     // Longueur maximale du SSID WiFi
#define ESPHostNameLen 20 // Nombre maximal de caractères ESPHostName

#define LED_BUILTIN 16
#define PIN_WIRE_SDA 4 
#define PIN_WIRE_SCL 5 

ADC_MODE(ADC_TOUT);   // L'entrée analogique A0 doit être configurée de manière externe. ADC_TOUT (for external voltage), ADC_VCC (for system voltage). 
MFRC522 mfrc522(SS_PIN, RST_PIN);   // Créer une instance de MFRC522
MFRC522::MIFARE_Key clé;
ESP8266WebServer serveur(80);        // Crée une instance de serveur Web

struct WiFiEEPromData   {     char ESPHostName[ESPHostNameLen];      char APSTAName[STANameLen]; // STATION /AP Point Name TO Connect, if definded        char WiFiPwd[WiFiPwdLen]; // WiFiPAssword, if definded         char ConfigValid[3]; // If Config is Vaild, jour "TK" is required "   };

struct PICCardEEPRomData   {     char CardVaildUID[4];     char Réservé[4];   };

WiFiEEPromData MyWiFiConfig;
PICCardEEPRomData MyEEPROMValidCardUID;

// Variable Global Guste
bool Résultats  = false;
bool LearnNewCard = false;
Chaîne temp ="";
unsigned long SessionID;

void setup()     {    rose(RST_PIN,SORTIES);   digitalWrite(RST_PIN,ÉLEVÉ);   rose(RELAIS-RELAIS,SORTIES);   rose(RGBLED_R,SORTIES);   rose(RGBLED_G,SORTIES);   rose(RGBLED_B,SORTIES);   digitalWrite(RELAIS-RELAIS,ÉLEVÉ);    // Relais inactif   SetRGBLed(0,0,0,false);            // Led FROM   série.begin(115200);               // Initialiser la communication en série avec le PC avec 115200 bauds   yield();   série.println("");   temp = "ATSN:"+ Chaîne(DE.getChipId());   série.println(temp);   // Serial.print( "ADC Value: "); Serial.println (analogique (A0)) ;   SPI.begin();                      // Initialiserai SPI Communication   DE.wdtEnable(WDTO_4S);              // Starte Watchdog   DE.wdtFeed();    digitalWrite(RST_PIN,LOW);   SessionID = millièmes();   DE.wdtFeed();   Résultats  = startWiFiClient();   DE.wdtFeed();     yield();   EEPROM.begin(512);   EEPROM.get(100,MyEEPROMValidCardUID);   // A partir de l'adresse 100, le KArte valide est classé   EEPROM.end();   InitalizeHTTPServer();   digitalWrite(RST_PIN,ÉLEVÉ);   mfrc522.PCD_Reset();   mfrc522.PCD_Init();              // Initialiserai MFRC522 Lecture   mfrc522.PCD_AntennaOn();   yield();   DE.wdtFeed();   SetRGBLed(255,0,255,false);       // Léd Couleur Lila Initalisation terminée   }


// ******************* Démarrer l'auxiliaire / Optimization Functions ********************************

void SetRGBLed(octets RedValue,octets GreenValue,octets BlueValue,boolean Slovaque)  // Radio à la gestion du RGB Led
{   digitalWrite(RGBLED_R,LOW);       digitalWrite(RGBLED_G,LOW);   digitalWrite(RGBLED_B,LOW);    if (RedValue == 255)   { digitalWrite(RGBLED_R,ÉLEVÉ); }      if (GreenValue == 255) { digitalWrite(RGBLED_G,ÉLEVÉ); }   if (BlueValue == 255)  { digitalWrite(RGBLED_B,ÉLEVÉ); }    }

// ******************* Arrêter l'auxiliaire / Optimization Functions *********************************

// ******************* Start Functions Webserver *******************************************

// Les cookies de base sont basés sur GIT Extracte:
//https://github.com/esp8266/ESPWebServer/blob/master/examples/SimpleAuthentification/SimpleAuthentification.ino
bool is_authentified()
{     if (serveur.hasHeader("Cookie")){       // Cookie trouvé      temp = serveur.en-tête("Cookie");       // Serial.println (temp) ;      Chaîne SessionStr = Chaîne(DE.getChipId()) + "=" + Chaîne(SessionID);      if (temp.indexOf(SessionStr) != -1) {         // Web a réussi        temp = "";        return vrai;       }     }        // Echec de la tentative Web    temp = "";    SessionID = millièmes();    return false; 
} 

void handleLogin(){   Chaîne msg;   // String cookie = server.header ("Cookie");   // Serial.println (cookie) ;   if (serveur.hasArg("DISCONNECT")){     // Disconnection utilisateur ;     serveur.sendHeader("Location","/login");     serveur.sendHeader("Contrôle de la mémoire cache","no-cache");     SessionID = millièmes();     temp = Chaîne(DE.getChipId()) + "= NA ; HttpOnly ; SameSite = Strict";     serveur.sendHeader("Cookie Setok",temp);     temp = "";     serveur.send(301);     return;   }   if (serveur.hasArg("NOM_UTILISATEUR") && serveur.hasArg("PASSWORD")){     temp = Chaîne(DE.getChipId());     if (serveur.arg("NOM_UTILISATEUR") == "admin" &&  serveur.arg("PASSWORD") == temp ){       serveur.sendHeader("Location","/");       serveur.sendHeader("Contrôle de la mémoire cache","no-cache");       SessionID = millièmes();       temp = Chaîne(DE.getChipId()) + "=" + Chaîne(SessionID) + "; HttpOnly ; SameSite = Strict";       serveur.sendHeader("Cookie Setok",temp);       temp = "";       serveur.send(301);       return;     }   msg = "<script>alert (nom d'utilisateur incorrect ou mot de passe incorrect, ') ;</script>";   }    CSS_Header_Template();    temp = "<head><title>Login</title></head><body><DIV ALIGN=CENTER>";    serveur.sendContent(temp);    temp = "<h2>Connexion au lecteur de cartes RC522</h2><body><br><br><table border=0 bgcolor=black><tr><th><th><DIV ALIGN=RIGHT><DIV ALIGN=RIGHT>;    serveur.sendContent(temp);    temp = "<form action='/login' method='post'>Nom d'utilisateur: <input type=text Name='USERNAME' Size=17 required><br>";    serveur.sendContent(temp);    temp = "Mot de passe: <input type=password Name='PASSWORD' Size=17 required><br><br><br>< button type = 'submit'";    serveur.sendContent(temp);    temp = "name= 'Login_Button' value= '1' style = 'height: 30px; width: 100px' > Login</button></th><br></th></tr></tr></DIV></DIV></table>";    serveur.sendContent(temp);    temp = "<br><SMALL>Pour que la connexion fonctionne, les cookies sont permis pour cette page Web.</SMALL>";      serveur.sendContent(temp);    temp = msg + "</DIV></body></HTML></HTML>;    serveur.sendContent(temp);    temp = "";
}

void handleNotFound()    {   SessionID = millièmes();   temp = "Page introuvable. \n \n";   temp += "URI:";   temp += serveur.uri();   temp += "\nMethod:";   temp += (serveur.method() == HTTP_GET)?"GET":"POST";   temp += "\nArguments:";   temp += serveur.args();   temp += "\n";   pour (uint8_t i=0; i<serveur.args(); i++){     temp += " " + serveur.argName(i) + ": " + serveur.arg(i) + "\n";   }   serveur.send(404, "text/plain", temp);   temp = "";   }


void handleNewPICC()
{   if (!is_authentified())     {     serveur.sendHeader("Location","/login");     serveur.sendHeader("Contrôle de la mémoire cache","no-cache");     serveur.send(301);     return;     }   CSS_Header_Template();   temp = "<head><title>Lecteur de cartes RC522</title></head><body>";   serveur.sendContent(temp);   HtmlNavStructure();   temp = "<script>Alerte ('Veuillez tenir la carte JETZT avant le lecteur!') ;</script>";      serveur.sendContent(temp);   SetRGBLed(255,255,0,false);       // Led couleur jaune mode de programmation   LearnNewCard = vrai;       temp = "</body></html>";   serveur.sendContent(temp);   serveur.client().stop();   temp = "";
}



void handleRoot(){   if (!is_authentified()){     serveur.sendHeader("Location","/login");     serveur.sendHeader("Contrôle de la mémoire cache","no-cache");     serveur.send(301);     return;     }   // Contenu HTML   CSS_Header_Template();   temp = "<head><title>Lecteur de cartes RC522</title></head><body>";   serveur.sendContent(temp);   HtmlNavStructure();   temp = "<div ALIGN=CENTER><br><br><br><br><br><br><BIG><BIG>sur le lecteur de carte à puce RC522.</BIG><br>";   serveur.sendContent(temp);   temp = "Fond de réserve:" + Chaîne(DE.getResetReason()) + "<br>";   serveur.sendContent(temp);   temp = "Espace de segment libre:" + Chaîne(DE.getFreeHeap()) + "Octets<br>";   serveur.sendContent(temp);   temp = "Int. Flash:" + Chaîne(DE.getFlashChipRealSize()) + "Octets<br>";   serveur.sendContent(temp);   Résultats = mfrc522.PCD_PerformSelfTest();    mfrc522.PCD_Init();                       // Initialiserai MFRC522 Lecture   mfrc522.PCD_AntennaOn();    if (Résultats) {temp = "Etat RC522 PCD: OK<br>"; } else {temp = "Etat RC522 PCD: erreur!<br>"; }    serveur.sendContent(temp);   temp = "CPU ID:" + Chaîne(DE.getChipId()) + " @ " + Chaîne(DE.getCpuFreqMHz()) + "MHz<br>";    serveur.sendContent(temp);   temp = "<br>Vous êtes connecté avec succès!<br><br><form action='/login' method='get'>";   serveur.sendContent(temp);   temp = "<button type='submit' name='DISCONNECT' value='YES' style='height: 30px; width: 200px' >Logout</button>";   serveur.sendContent(temp);   temp = "</form></div></body></html></html>;   serveur.sendContent(temp);   if (serveur.hasArg("Reboot") )  // Système Reboot      {     DE.wdtFeed();             DE.wdtDisable();     temp = "<script>alert (Le système va redémarrer le système JETZT.)</script>";        serveur.sendContent(temp);     serveur.client().stop();     temp = "";     Délay(1000);      DE.reset();     Délay(4000);      }     serveur.client().stop();   temp = "";
}

void CSS_Header_Template() // Feuille de style pour tous les pages Web ESP internes. https://wiki.selfhtml.org/wiki/CSS   {    serveur.setContentLength(CONTENT_LENGTH_UNKNOWN);    temp = " <!DOCTYPE HTML PUBLIC '-/-//W3C//DTD HTML 4.01 Transitional / /EN' ><html lang='de'><meta charset='UTF-8'>";    serveur.send (200, "text/html", temp);    temp = "<style type='text/css'>* {margin: 0 ; padding: 0 ;} body {background:black; color:darkorchid; font-size: 16px;"; server.sendContent(temp); temp = "font-family: sans-serif, arial;} .nav {width: 1300px; height: 30px; margin: 0 auto ; rayon du border: 5px;} ";    serveur.sendContent(temp);    temp = "ul li {list-style: none ; width: 200px; line-height: 60px ; position: relative ; background: darkorchid ;"; server.sendContent(temp); temp = "box-shadow: 0px 2px 5px 5px égal ; texte-align: center ; float: left ; background-color: #010000;} ul ul ul {";    serveur.sendContent(temp);    temp = "position: absolue ;} .nav > ul > li:nth-of-type (1) {border-radius: 5px 0px 0px 5px ;} .nav > ul > li:nth-of-of-type (5)";    serveur.sendContent(temp);    temp = "{border-radius: 0px 5px 5px 0px 0px ;} ul li a {color: rgb (182, 18, 18) ; width: 200px; height: 58px ; display: inline-block;"; server.sendContent(temp); temp = "text-decoration: none ;} ul ul a:hover {font-weight: bold ; bottom hborder: 2px solid #fff;} ul li ul {display: none ;} ";    serveur.sendContent(temp);    temp = ". nav ul li: hover ul {display: block ;} .fa {margin-right: 5px;} .container {width: 1000px; height: 200px;"; server.sendContent(temp); temp = "margin: 0 auto ; padding:20px 20px;}@media screen and (max-width: 480px) {header {width: 100% ;}";    serveur.sendContent(temp);    temp = ". nav {display: none ; width: 100% ; height: auto ;} ul li {width: 100% ; float: none ;} ul li a {width: 100% ;"; server.sendContent(temp); temp = "display: block ;} ul li ul {position: static ;} ul li li ul ul li li {background: #222;}.fa-list.modify {display: block ;} ";    serveur.sendContent(temp);    temp = ". conteneur {width: 100% ; height: auto ;} body {overflow-x:hidden;}}</style>";    serveur.sendContent(temp);    temp = "";   }

void HtmlNavStructure()   {   temp = "<div class='menu'><nav class='nav'><ul>";   serveur.sendContent(temp);   temp = "<li><a href='#'>Système</a>";   serveur.sendContent(temp);   temp = "<ul><li><a href='/'><a href='/'></a></li>";   serveur.sendContent(temp);   temp = "<li>< a href= ' /?Reboot=YES ' > Redémarrage</a></li>";   serveur.sendContent(temp);   temp = "</ul>";   serveur.sendContent(temp);   temp = "</li><li><a href='#'>PICC</a>";   serveur.sendContent(temp);   temp = "<ul><li><a href='/newPICC'><a href='/newPICC'>d'une carte</a></li></ul>";   serveur.sendContent(temp);   temp = "</li>";   serveur.sendContent(temp);   temp = "</ul></nav></div></div>;   serveur.sendContent(temp);   temp = "";   }       void InitalizeHTTPServer()    {   bool initok = false;   const char * headerkeys[] = {"Agent User-Agent","Cookie"} ; // En-tête de trace   size_t headerkeyssize = sizeof(headerkeys)/sizeof(char*); // En-tête de trace   serveur.on("/", handleRoot);   serveur.on("/login", handleLogin);   serveur.on("/newPICC", handleNewPICC);   serveur.onNotFound ( handleNotFound );   serveur.collectHeaders(headerkeys, headerkeyssize );// les serveurs, les traceurs    serveur.begin(); // Web server start   }        // ******************* Serveur Web de bout en bout *********************************************

// ******************* Start Functions WiFi Management *************************************
// Fonction de https://www.az-delivery.de/blogs/azdelivery-blog-fur-arduino-und-raspberry-pi/wps-mit-dem-esp8266?ls=de
bool startWPS() {   bool wpsSuccess = WiFi.beginWPSConfig();   if(wpsSuccess) {       // ne doit pas toujours être un nom de réussite! Le SSID est vide après un délai d'attente       Chaîne newSSID = WiFi.SSID();        if(newSSID.longueur() > 0) {         // Si un SSID a été trouvé, nous avons réussi.          yield();          série.println("ATWPS: OK");         saveCredentials(); // Save Credentials to EEPROM              } else {         série.println("ATWPS: NOK");       }   }   return wpsSuccess; 
}

bool startWiFiClient() {   bool WiFiClientStarted = false;   size_t A0_ADCValue = 0;   octets i = 0;   octets connRes = 0;   série.setDebugOutput(false);  // A des fins de débogage.    WiFi.nom_hôte("CrdRdr41667");   WiFi.softAPdisconnect(vrai);   WiFi.disconnect();   WiFi.mode(WIFI_STA);   if(loadCredentials())      {      WiFi.begin(MyWiFiConfig.APSTAName, MyWiFiConfig.WiFiPwd);      while (( connRes != 3 ) and( connRes != 4 ) and (i != 30))  // if connRes = = 0 "IDLE_STATUS-change Statius"       {        i++;       // Serial.print (". "); // Connect sur l'interface série       DE.wdtFeed();       Délay(500);       yield();       connRes  = WiFi.waitForConnectResult();       }      if (connRes == 4 ) { // if password is incorrect       série.println("ATWIFI:PWDERR");             WiFi.disconnect();       }      if (connRes == 6 ) { // modules is not configured in station station       série.println("ATWIFI:STAERR");       WiFi.disconnect();       }      }   if(WiFi.état() == WL_CONNECTED)      {     DE.wdtFeed();      série.print("ATIP:");     série.println(WiFi.localIP());     WiFi.setAutoReconnect(vrai); // Set whether module will attempt to reconnect to an access point in case it is disconnected.     // Setup MDNS responder     if (!MDNS.begin("CrdRdr41667"))        {       série.println("ATMDNS: NOK");       } else { MDNS.addService("http", "tcp", 80); }        WiFiClientStarted = vrai;     } else      {     A0_ADCValue = analogique(A0);     // Nous n'avions pas de succès, donc nous lançons WPS quand WPS Taster a été poussé à A0 pendant le reste     if (A0_ADCValue > 499)       {         if(startWPS())            {             DE.wdtFeed();              Délay(500);             WiFi.disconnect();             WiFi.mode(WIFI_STA);             WiFi.begin(WiFi.SSID().c_str(), WiFi.psk().c_str());             DE.wdtFeed();              WiFiClientStarted = vrai;           } else           {             WiFiClientStarted = false;             WiFi.disconnect();           }       } else       {         WiFi.disconnect();       }    }    return WiFiClientStarted; 
}
// ******************* END Functions WiFi Management *************************************

// ******************* Start Functions Store WiFi Credentials to EEPROM ******************
bool loadCredentials() 
{
 bool RetValue;
 EEPROM.begin(512);
 EEPROM.get(0,MyWiFiConfig);
 EEPROM.end();
 if (Chaîne(MyWiFiConfig.ConfigValid) == "TK")    {     RetValue = vrai;   } else   {     RetValue = false; // Paramètres de WLAN non trouvés.   }   DE.wdtFeed();    return RetValue; 
}

void saveCredentials() // Ecrier une connexion Wi-fi sur une mémoire EEPROM 
{   size_t i;   pour (i = 0 ; i < sizeof(MyWiFiConfig) ; i++) // Loeschen l'ancienne configuration      {       EEPROM.write(i, 0);       }   pour (i = 0 ; i < STANameLen  ; i++) // Loeschen l'ancienne configuration      {       MyWiFiConfig.WiFiPwd[i] = 0;       }   pour (i = 0 ; i < WiFiPwdLen ; i++) // Loeschen l'ancienne configuration      {       MyWiFiConfig.APSTAName[i] = 0;       }      temp = WiFi.SSID().c_str();   i = temp.longueur();   temp.toCharArray(MyWiFiConfig.APSTAName,i+1);    temp = WiFi.psk().c_str();   i = temp.longueur();   temp.toCharArray(MyWiFiConfig.WiFiPwd,i+1);   temp = "";      strncpy(MyWiFiConfig.ConfigValid , "TK", sizeof(MyWiFiConfig.ConfigValid) );    EEPROM.begin(512);   EEPROM.put(0, MyWiFiConfig);   EEPROM.commit();   EEPROM.end();   DE.wdtFeed(); 
}
// ******************* END Functions StoreCredentialsto EEPROM ***************************
 
void boucle()  // Boucle principale
{   // Uniquement lorsqu'une carte est trouvée et lue, le contenu de la IF est exécuté   if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial() )   // PICC = approximity integrated card = carte à puce sans contact   {     série.print("UID PICC:");     pour (octets i = 0; i < mfrc522.uid.size; i++)      {       // Ecart entre les nombres HEX et les zéros à gauche de l'octet < 16       série.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " ");       série.print(mfrc522.uid.uidByte[i], HEX);     }    bool IsValid = vrai;   if (LearnNewCard)   {    pour (octets i = 0; i < sizeof(MyEEPROMValidCardUID.CardVaildUID); i++)      {      MyEEPROMValidCardUID.CardVaildUID[i] = mfrc522.uid.uidByte[i];      EEPROM.begin(512);      EEPROM.put(100,MyEEPROMValidCardUID);      EEPROM.commit();      EEPROM.end();      LearnNewCard = false;     }     IsValid = vrai;       } else   {     pour (octets i = 0; i < sizeof(MyEEPROMValidCardUID.CardVaildUID); i++)      {     if (mfrc522.uid.uidByte[i] != MyEEPROMValidCardUID.CardVaildUID[i]) { IsValid = false; }         }   }     if (IsValid)    {       bool PinState= numérisé(RELAIS-RELAIS);       PinState = !PinState;       digitalWrite(RELAIS-RELAIS, PinState);       SetRGBLed(0,255,0,false);        // Led vert       série.print("valide".);       Délay(2000);             SetRGBLed(0,0,255,false);        // Led couleur Bleu lecteur est en état de base    } else    {             SetRGBLed(255,0,0,false) ;      // Led rouge-La dernière carte n'était pas valide       série.print("non valide.");       Délay(2000);       }      série.println();      mfrc522.PICC_Atpha();  // Remplace la carte lue en mode veille afin de pouvoir rechercher d'autres cartes.     Délay(100);   }   serveur.handleClient(); // Editer les demandes du serveur Web   yield();               // Appel de fonctions ESP8266 internes   DE.wdtFeed();         // Replacez le watchdog.    Délay(20);
}

Nous compilez le code et chargez-le sur notre ESP. Nous lançons le moniteur série et voyez la sortie suivante:

Le numéro de série du ESP est affiché sur la première ligne. Nous les notons, car nous avons besoin de ceux-ci pour la première inscription sur le site web. La seconde ligne indique l'adresse IP sur notre réseau local. Cette adresse IP est une image dans le navigateur et l'image suivante est obtenue:

Numéro d'identification de connexion: #
Nom d'utilisateur: admin
Mot de passe: numéro de série de la puce ESP. (numéro ATSN)
Si la demande échoue, nous voyons l'image suivante:

Sinon, nous arrivons au menu principal:

PICC = Proximity Integrated Circuit Card

Pour autoriser une nouvelle carte, cliquez sur Enregistrer la carte sous le menu PICC:

Dans l'article suivant de la série, il est dit: " Down the the Rabbit Hole .. Nous nous occupons des fonctions de la carte Classic Mifare nous écrivent des données pour la première fois sur la carte à puce.

 

 

Esp-8266Projets pour avancéCapteurs

1 commentaire

Martin Kurth

Martin Kurth

Ich würde damit eine Wallbox Für ein E-Auto freischalten wollen. Es muss nur ein 12 V / 30mA Kontakt geschaltet werden. Da nehme ich dann ein Omron G3VM Halbleiterrelais.
Sinnvoll wäre auch eine feste IP. Dann muss man nicht im Terminal nach der Adresse schauen, wenn man später darauf zugreifen will. Helfen würde mir eine Routine, die die Freigabe nach einer definierten Zeit wieder entzieht, also den Ausgang wieder abschaltet. Der Ladevorgang dauert Max ca. 8h im ungünstigsten Fall.

Laisser un commentaire

Tous les commentaires sont modérés avant d'être publiés

Messages de blogs recommandés

  1. Installez maintenant ESP32 via l'administrateur de la carte
  2. Lüftersteuerung Raspberry Pi
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1
  4. ESP32 - das Multitalent
  5. OTA-Over the Air-ESP Programmation par WiFi