ESP32 + D1 Mini über ESP-Now verbinden

Im Gegensatz zur herkömmlichen Art einen Controller über WLAN mit einem anderen zu verbinden, benutzt ESP-Now ein spezielles Protokoll, das zwei WLAN-fähige Microcontroller direct miteinander verbindet ohne über einen Router zu gehen. Die Verbindung erfolgt direkt mittels der jeweiligen MAC Adressen und kann natürlich nur innerhalb eines WLANs erfolgen.

Durch dieses vereinfachte Protokoll geht der Verbindungsaufbau viel schneller und damit reduziert sich auch der Stromverbrauch des Controllers. ESP-Now hat allerdings auch Nachteile, die Datenmenge pro Paket ist auf 250 Bytes begrenzt und das Internet kan nur über ein Gateway erreicht werden.

Ein Master kann mit bis zu 20 Slaves gepaart werden. Will man das ESP-Now Netzwerk verschlüsseln, reduziert sich die Anzahl der möglichen Slaves auf 10.

In unserem Beispiel werden wir einen ESP32 als Master und einen D1 Mini als Slave verwenden. Der Slave wird als Soft Access Point gestartet, sodass der Master ihn durch einen Netzwerk-Scan finden kann.

Der Slave soll auf Anforderung des Masters Meßwerte von einem Temperatur und Feuchte Sensor senden.

Slave:

Der Slave ist ein D1 Mini der über den Pin D0 = GPIO 16 mit dem Data Pin eines DHT11 Temperatursensors verbunden wird.

Der folgende ausführlich kommentierte Sketch zeigt das verwendete Programm. Der Access Point exportiert das WLAN Netzwerk mit dem Namen Slave_1, das Passwort wird für den Verbindungsaufbau nicht benötigt.  :

 

/* D1 Mini mit DHT11 Temperatur und Feuchte Sensor als
 *  Slave in einer ESP-NOW Kommunikation
 *  Der Slave erzeugt das WiFi Netz Slave_1 und wartet auf Daten vom Master
 *  Immer wenn ein Datenpaket empfangen wurde antwortet der Slave mit
 *  den Messwerten vom Sensor
 */

 //Bibliotheken für WiFi und für den Sensor
#include <ESP8266WiFi.h>
#include <SimpleDHT.h>

//Bibliothek für ESP-Now
extern "C"{
#include <espnow.h>
}

//WiFi Kanal
#define CHANNEL 1

//Datenstruktur für den Datenaustausch
struct DATEN_STRUKTUR {
    uint16_t temperatur = 0;
    uint32_t feuchte = 0;
};

//Instanz zum Zugriff auf den Sensor
SimpleDHT11 dht11;

//Datenpin am D1Mini = D0 entspricht GPIO16 
const byte dataPin = 16; //Daten pin des DHT11

//globale Variablen für Messwerte
byte temperatur = 0;
byte feuchte = 0;

//Wir brauchen einen WiFi Access Point
void configDeviceAP() {
  char* SSID = "Slave_1";
  bool result = WiFi.softAP(SSID, "Slave_1_Password", CHANNEL, 0);
  if (!result) {
    Serial.println("AP Config failed.");
  } else {
    Serial.println("AP Config Success. Broadcasting with AP: " + String(SSID));
  }
}

//Callback funktion wenn Daten empfangen wurden 
void on_receive_data(uint8_t *mac, uint8_t *r_data, uint8_t len) {
    //zur Information wandeln wir die MAC Adresse des Absenders in einen String und geben sie aus
    char MACmaster[6];
    sprintf(MACmaster, "%02X:%02X:%02X:%02X:%02X:%02X",mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]);
    Serial.print("Empfang von MAC: "); Serial.print(MACmaster);
 
    DATEN_STRUKTUR ed;
    //wir kopieren die empfangenen Daten auf die Datenstruktur um
    //über die Datenstruktur zugreifen zu können
    memcpy(&ed, r_data, sizeof(ed));

    //hier könnten wir über ed.temperatur und ed.feuchte auf jene Daten zugreifen
    //die der Absender geschickt hat
 
    //nun setzen wir die Werte mit den Daten vom Sensor
    ed.temperatur = temperatur;
    ed.feuchte = feuchte;

    //wir kopieren die daten in einen Speicherblock und senden sie zurück an den Absender
    uint8_t s_data[sizeof(ed)]; memcpy(s_data, &ed, sizeof(ed));
    esp_now_send(mac, s_data, sizeof(s_data));
  
 
  };
 

void setup() {
  Serial.begin(115200); Serial.println();
  //Konfiguration des Access Points
  WiFi.mode(WIFI_AP);
  configDeviceAP(); 
  //ESOP-Now initialisieren  
  if (esp_now_init()!=0) {
    Serial.println("Protokoll ESP-NOW nicht initialisiert...");
    ESP.restart();
    delay(1);
  }
 
  //***MAC Adressenb zur Info ***//
  Serial.print("AP MAC: "); Serial.println(WiFi.softAPmacAddress());
  Serial.print("STA MAC: "); Serial.println(WiFi.macAddress());
 
  // ESP Rolle festlegen 1=Master, 2 = Slave 3 = Master + Slave
  esp_now_set_self_role(2); 
  //und callback funktion registrieren
  esp_now_register_recv_cb(on_receive_data);  
 
}

void loop() {
  
  //messwerte vom Sensor mit Fehlerüberprüfung
  int err = SimpleDHTErrSuccess;
  err = dht11.read(dataPin, &temperatur, &feuchte, NULL);
  if (err != SimpleDHTErrSuccess) {
    Serial.print("Fehler beim Lesen vom DHT11. Fehler = ");
    Serial.println(err);
  }
  // warte 1.5 s Der DHT11 macht 1 Messung / s
  delay(1500);
  
}




Master:

Für den Master habe ich einen ESP-32 Developmentboard verwendet. Natürlich ist auch hier jedes andere ESP-Board verwendbar. Der Master wird im Station mode gestartet. In seiner Hauptschleife Skannt er solange die verfügbaren WiFi Netze bis er ein Netz gefunden hat dessen Name mit "Slave" beginnt. Hat er das Netz gefunden ermittelt er die MAC Adresse des Access Points, die für die weitere Kommunikation verwendet wird. Auch wenn das Slave Netzwerk zwischenzeitlich ausfällt, muss der Master keinen weiteren Scan durchführen.

Hier folgt nun der Sketch für den Master:

 

 

/* ESP32 als Master für eine ESP-Now verbindung
 *  Der ESP32 sucht nach einem WiFi Netzerk dessen Namen mit Slave beginnt
 *  Wenn er so ein Netzwerk gefunden hat merkt er sich die MAC-Adresse des
 *  Slaves und kann mit diesem über ESP_Now kommunizieren
 */

//Bibliotheken für Esp-Now und für WiFi einbinden
#include <esp_now.h>
#include <WiFi.h>

// Globale Kopie der Slave Information
// und ein Flag um zu wissen ob der Slave bereits gefunden wurde
bool slaveFound = 0;
esp_now_peer_info_t slave;

#define CHANNEL 3

//Datenstruktur für den Datenaustausch
struct DATEN_STRUKTUR {
    uint16_t temperatur = 0;
    uint32_t feuchte = 0;
};
 

// Init ESP Now mit Restart falls was schief geht
void InitESPNow() {
  if (esp_now_init() == ESP_OK) {
    Serial.println("ESPNow Init Success");
  }
  else {
    Serial.println("ESPNow Init Failed");
    //Fehler also neuer Versuch
    ESP.restart();
  }
}

// Wir suchen einen Slave
void ScanForSlave() {
  int8_t scanResults = WiFi.scanNetworks();
  // reset on each scan
  memset(&slave, 0, sizeof(slave));

  Serial.println("");
  if (scanResults == 0) {
    Serial.println("NoKein WiFi Gerät im AP Modus gefunden");
  } else {
    Serial.print(scanResults); Serial.println(" Geräte gefunden ");
    for (int i = 0; i < scanResults; ++i) {
      // Print SSID and RSSI for each device found
      String SSID = WiFi.SSID(i);
      int32_t RSSI = WiFi.RSSI(i);
      String BSSIDstr = WiFi.BSSIDstr(i);

      // Prüfen ob der Netzname mit `Slave` beginnt
      if (SSID.indexOf("Slave") == 0) {
        // Ja, dann haben wir einen Slave gefunden
        Serial.println("Slave gefunden.");
        Serial.print(i + 1); Serial.print(": "); Serial.print(SSID); Serial.print(" ["); Serial.print(BSSIDstr); Serial.print("]"); Serial.print(" ("); Serial.print(RSSI); Serial.print(")"); Serial.println("");
        // MAC-Adresse aus der BSSID ses Slaves ermitteln und in der Slave info struktur speichern
        int mac[6];
        if ( 6 == sscanf(BSSIDstr.c_str(), "%x:%x:%x:%x:%x:%x%c",  &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5] ) ) {
          for (int ii = 0; ii < 6; ++ii ) {
            slave.peer_addr[ii] = (uint8_t) mac[ii];
          }
        }

        slave.channel = CHANNEL; // pick a channel
        slave.encrypt = 0; // no encryption

        slaveFound = 1;
        //Ohne dieses break könnte man auch mit mehr als einem Slave verbinden
        //Wenn keine Verschlüsselung verwendet wird sind bis zu 20 Slaves möglich
        break;
      }
    }
  }

  // clean up ram
  WiFi.scanDelete();
}

// Prüfe ob ein Slave bereits gepaart is
// Sonst wird der Slave mit dem Master gepaart
bool manageSlave() {
  if (slave.channel == CHANNEL) {
    const esp_now_peer_info_t *peer = &slave;
    const uint8_t *peer_addr = slave.peer_addr;
    // check if the peer exists
    bool exists = esp_now_is_peer_exist(peer_addr);
    if ( !exists) {
      // Slave not paired, attempt pair
      Serial.print("Slave Status: ");
      esp_err_t addStatus = esp_now_add_peer(peer);
      if (addStatus == ESP_OK) {
        // Pair success
        Serial.println("Pair success");
        return true;
      } else if (addStatus == ESP_ERR_ESPNOW_NOT_INIT) {
        // How did we get so far!!
        Serial.println("ESPNOW Not Init");
        return false;
      } else if (addStatus == ESP_ERR_ESPNOW_ARG) {
        Serial.println("Invalid Argument");
        return false;
      } else if (addStatus == ESP_ERR_ESPNOW_FULL) {
        Serial.println("Peer list full");
        return false;
      } else if (addStatus == ESP_ERR_ESPNOW_NO_MEM) {
        Serial.println("Out of memory");
        return false;
      } else if (addStatus == ESP_ERR_ESPNOW_EXIST) {
        Serial.println("Peer Exists");
        return true;
      } else {
        Serial.println("Not sure what happened");
        return false;
      }
    }
  } else {
    // No slave found to process
    Serial.println("No Slave found to process");
    return false;
  }
}

// sende Daten an Slave
void sendData() {
 DATEN_STRUKTUR ed;  
    //Da wir die Daten in diesem Beispiel im Slave ignorieren
    //ist es gleichgültig, was wir senden                   
    ed.temperatur = 0;
    ed.feuchte = 0;
    //wir kopieren die Datenstruktur in einen Speicherblock
    uint8_t data[sizeof(ed)]; memcpy(data, &ed, sizeof(ed));
    const uint8_t *peer_addr = slave.peer_addr;
    //Wir senden die Daten und prüfen den Status
  Serial.print("Sending: ");
  esp_err_t result = esp_now_send(peer_addr, data, sizeof(data));
  Serial.print("Send Status: ");
  if (result == ESP_OK) {
    Serial.println("Success");
  } else if (result == ESP_ERR_ESPNOW_NOT_INIT) {
    // How did we get so far!!
    Serial.println("ESPNOW not Init.");
  } else if (result == ESP_ERR_ESPNOW_ARG) {
    Serial.println("Invalid Argument");
  } else if (result == ESP_ERR_ESPNOW_INTERNAL) {
    Serial.println("Internal Error");
  } else if (result == ESP_ERR_ESPNOW_NO_MEM) {
    Serial.println("ESP_ERR_ESPNOW_NO_MEM");
  } else if (result == ESP_ERR_ESPNOW_NOT_FOUND) {
    Serial.println("Peer not found.");
  } else {
    Serial.println("Not sure what happened");
  }
}

// callback wenn die Daten zum Slave gesendet wurden
// hier sieht man wenn der Slave nicht erreichbar war
void on_data_sent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  char macStr[18];
  snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
           mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
  Serial.print("Last Packet Sent to: "); Serial.println(macStr);
  Serial.print("Last Packet Send Status: "); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}


// callback wenn wir Daten vom Slave bekommen
void on_data_recv(const uint8_t *mac_addr, const uint8_t *data, int data_len) {
  char macStr[18];
  //MAC Adresse des Slaves zur Info
  snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
           mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
  DATEN_STRUKTUR ed;
  //wir kopieren die Daten in die Datenstruktur
    memcpy(&ed, data, sizeof(ed));
    //und zeigen sie an
    Serial.print("Empfangen von "); Serial.println(macStr);
    Serial.print("Temperatur: "); Serial.print(ed.temperatur); Serial.println(" °C");
    Serial.print("Feuchte   : "); Serial.print(ed.feuchte); Serial.println(" %");
}

void setup() {
  Serial.begin(115200);
  //Set device in STA mode to begin with
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  Serial.println("ESPNow ESP32 als Master");
  // das ist die mac Adresse vom Master
  Serial.print("STA MAC: "); Serial.println(WiFi.macAddress());
  // Init ESPNow with a fallback logic
  InitESPNow();
  //Wir registrieren die Callback Funktion am Ende des sendevorgangs
  esp_now_register_send_cb(on_data_sent);
  //Wir registrieren die Callback Funktion für den Empfang
  esp_now_register_recv_cb(on_data_recv);
}

void loop() {
  // Wenn wir noch keinen Slave gefunden haben suchen wir weiter
  if (!slaveFound) ScanForSlave();
  if (slaveFound) {
    //haben wir einen Slave muss er gepaart werden
    //falls das noch nicht geschehen ist
    bool isPaired = manageSlave();
    if (isPaired) {
      // Wenn Master und Slave gepaart sind, können wir senden
      sendData();
    } else {
      // slave pair failed
      Serial.println("Slave pair failed!");
    }
  }
  else {
    // No slave found to process
  }

  // 5 Sekunden warten
  delay(5000);
}

  Tipp:

Man kann von einem PC mit der Arduino IDE mehr als ein Board zur gleichen Zeit zu bearbeiten. Dazu ist es lediglich notwendig die IDE in zwei Instanzen zu starten , also nicht mehrere Fenster der gleichen Instanz. Dann können Board Typ und Port-Nummer getrennt eingestellt werden.

Test:

Wir starten zuerst den Master: Der serielle Monitor wird anzeigen, dass der Master kein geeignetes Netz findet. Wenn wir nun den Slave starten, sollte der Master das Netz finden und eine Verbindung herstellen. Der Master sollte dann die Temperatur und Feuchte die er vom Slave geliefert bekommt anzeigen.

Wenn wir den Slave abschalten wird der Master weiter versuchen den Slave zu erreichen, was aber in einem Fehler endet. Sobald wir den Slave wieder einschalten, kann der Master wieder erfolgreich Daten senden und empfangen.

ESP-Now werden wir auch im Home-Control Projekt einsetzen.

Viel Spass beim Experimentieren.

 

 

Letzter Artikel PlatformIO: Erste Schritte

Kommentar

W. Lindner - September 4, 2018

Hallo.
Code funktioniert grundsätzlich.
Habe das an den ESP32 angepasst:
mit
- #include
- esp_now_set_self_role(2); entfernt

Master sendet, Slave empfängt, Slave sendet zurück;
aber beim Master on_data_recv(…) kommen keine Daten an.

Eine Idee dazu ?

Grüße
wolf

Hinterlasse einen Kommentar

Kommentare müssen vor der Veröffentlichung überprüft werden

Erforderliche Angabe