Funkschalter mit ESP-Now

In diesem Beitrag steuern wir ein Relais über WiFi mit einer einfachen Batterie betriebenen Fernsteuerung. Die Reichweite ist dank WiFi sehr gut.

Als Bauteile benötigen wir einen ESP8266-01S mit Relais, einen ESP8266-01S und einen einfachen Taster. Zum Programmieren der beiden ESP8266-01S benötigen wir noch einen USB-Adapter.

Für die Versorgung des Relais-Moduls bietet sich das Mini-Netzteil 5V an. Ein Gehäuse zum Einbau des Relais-Moduls plus Mini-Netzteil in eine handelsübliche Schalterdose, das mit einem 3-Drucker gedruckt werden kann, wird folgen.

Der ESP8266 auf dem Relais Modul bildet einen Access Point mit SSID "Funkschalter". Der Access-Point kann ESP-Now Nachrichten empfangen. Je nach empfangener Nachricht wird das Relais ein oder ausgeschaltet.

Für die Fernbedienung verwenden wir ebenfalls einen ESP8266, der einen AccessPoint mit SSID "Funkschalter" sucht und sich die MAC Adresse merkt. Dann sendet er an diese MAC Adresse die Nachricht das Relais einzuschalten. Dieser Relaiszustand wird ebenfalls gespeichert. Dann geht der ESP8266 in den Tiefschlaf Modus, in dem er nur einige uA verbraucht.

Der Taster an der Fernbedienung löst einen Reset am ESP8266 aus. Wurde in der Zwischenzeit die Stromversorgung nicht unterbrochen, hat sich die Fernbedienung die MAC Adresse gemerkt und kann unmittelbar die Nachricht ans Relais senden. Die Nachricht wird mit jedem Reset umgekehrt sodass, das Relais abwechselnd den Befehl zum Ein- und Ausschalten erhält.

Damit man in einem Haushalt mehrere solcher Funkschalter einsetzen kann, versteckt der Accesspoint nach ca. fünf Minuten seine SSID. Um also eine Fermbedienung mit dem Funkschalter zu verbinden gibt man einen Reset auf das Relaismodul wodurch die SSID für fünf Minuten sichtbar wird. Nun drückt man den Knopf der Fernbedienung und diese kann den Accesspoint finden und die MAC Adresse speichern. Sollte die Fernbedienung mit einem anderen Relaismodul verbunden werden, muss die Stromversorgung der Fernbedienung kurz unterbrochen werden, sodass die Fernbedienung die MAC Adresse vergisst.

Schaltung:

 

Damit man unnötigen Strom vermeidet, sollte die rote LED die immer leuchtet wenn die Stromversorgung aktiv ist, entfernt werden.

Sketch Relais Modul:

 

/* Esp8266 mit Relais als ferngesteuerter Schalter. 
 */

 //Bibliotheken für WiFi 
#include <ESP8266WiFi.h>

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

//WiFi Kanal
#define CHANNEL 4
#define SSID "Funkschalter_1"

//Datenstruktur für den Datenaustausch
struct DATEN_STRUKTUR {
    uint16_t relais = 0;
};


byte RELAIS_OFF[] = {0xA0,0x01,0x01,0xa2};
byte RELAIS_ON[] = {0xA0,0x01,0x00,0xa1};
boolean ssid_hidden;

//Wir brauchen einen WiFi Access Point
void configDeviceAP() {
  //NULL = kein Passwort, 0 = SSID sichtbar
  bool result = WiFi.softAP(SSID,NULL,CHANNEL,0);
}


//Callback funktion wenn Daten empfangen wurden 
void on_receive_data(uint8_t *mac, uint8_t *r_data, uint8_t len) {
 
    DATEN_STRUKTUR data;
    //wir kopieren die empfangenen Daten auf die Datenstruktur um
    //über die Datenstruktur zugreifen zu können
    memcpy(&data, r_data, sizeof(data));

    if (data.relais == 12345)  {
        Serial.write(RELAIS_ON,sizeof(RELAIS_ON));
    } 
    if (data.relais == 54321)  {
        Serial.write(RELAIS_OFF,sizeof(RELAIS_OFF));
    }
 
  };
 

void setup() {
  Serial.begin(9600); 
  Serial.println();
  //Relais ausschalten
  Serial.write(RELAIS_OFF,sizeof(RELAIS_OFF));
  WiFi.begin();
  //Konfiguration des Access Points
  WiFi.mode(WIFI_AP);
  
  configDeviceAP(); 
  //ESP-Now initialisieren  
  if (esp_now_init()!=0) {
    ESP.restart();
    delay(1);
  }
  ssid_hidden = false;
 
 
  // 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() {
    //5 minuten warten, dann SSID verstecken
    delay(300000);
    if (!ssid_hidden) {
      //1 = SSID versteckt
      WiFi.softAP(SSID,NULL,CHANNEL,1);
      ssid_hidden = true;
    }
  
}


 

Sketch Fernbedienung:

 

/*
 WLAN Schalter zum Steuern eines Relais.
 Der Schalter basiert auf einem ESP8266-01 und benutzt ESPNOW
 Nachdem der Schalter einmal mit Strom versorgt wurde sucht er nach einem
 AP mit einer SSID die mit "Funkschalter" beginnt. Findet er einen geeigneten
 AP merkt er sich die MAC Adresse im RTC Memory und sendet den Status an das Relais.
 Danach geht er für immer in den Tiefschlaf Modus.
 Durch betätigen der Reset Taste wird er wieder aufgeweckt und kann mit der MAC Adresse
 sofort das Relais ansteuern. Wird die Stromzufuhr unterbrochen sucht der Schalter erneut den AP.
*/
#include <ESP8266WiFi.h>
extern "C" {
  #include <espnow.h>
}

//wenn diese Konstante auf false ist werden keine Nachrichten ausgegeben und die 
//Serielle Schnittstelle nicht aktiviert um Strom zu sparen.
#define DEBUG true

//Globale Konstanten
#define WIFI_CHANNEL 4
#define SEND_TIMEOUT 2450  // 245 Millisekunden timeout 

//Datenstruktur für den Datenaustausch
struct DATEN_STRUKTUR {
    uint16_t relais = 0;
};

//Datenstruktur für das Rtc Memory mit Prüfsumme um die Gültigkeit
//zu überprüfen
struct MEMORYDATA {
  uint32_t crc32;
  byte status; //letzter Relais Zustand
  uint8_t mac[6];
};

//Globale daten
volatile bool callbackCalled;
MEMORYDATA statinfo;

//Unterprogramm zum Berechnen der Prüfsumme
uint32_t calculateCRC32(const uint8_t *data, size_t length)
{
  uint32_t crc = 0xffffffff;
  while (length--) {
    uint8_t c = *data++;
    for (uint32_t i = 0x80; i > 0; i >>= 1) {
      bool bit = crc & 0x80000000;
      if (c & i) {
        bit = !bit;
      }
      crc <<= 1;
      if (bit) {
        crc ^= 0x04c11db7;
      }
    }
  }
  return crc;
}

//Schreibt die Datenstruktur statinfo mit korrekter Prüfsumme in das RTC Memory
void UpdateRtcMemory() {
    uint32_t crcOfData = calculateCRC32(((uint8_t*) &statinfo) + 4, sizeof(statinfo) - 4);
    statinfo.crc32 = crcOfData;
    ESP.rtcUserMemoryWrite(0,(uint32_t*) &statinfo, sizeof(statinfo));
}

//sucht nach einem geeigneten AccessPoint
void ScanForSlave() {
  WiFi.disconnect();
  int8_t scanResults = WiFi.scanNetworks();
  // reset on each scan
  bool slaveFound = 0;

  if (DEBUG) Serial.println("");
  if (scanResults == 0) {
    if (DEBUG) Serial.println("No WiFi devices in AP Mode found");
  } else {
    if (DEBUG) {
      Serial.print("Found "); 
      Serial.print(scanResults);
      Serial.println(" devices ");
    }
    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);

      if (DEBUG) {
        Serial.print(i + 1);
        Serial.print(": ");
        Serial.print(SSID);
        Serial.print(" (");
        Serial.print(RSSI);
        Serial.print(")");
        Serial.println("");
      }
      delay(10);
      // Prüfen ob die SSID mit Funkschalter beginnt
      if (SSID.indexOf("Funkschalter") == 0) {
        // SSID gefunden
        if (DEBUG) {
          Serial.println("Funkschalter 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("");
        }
        int mac[6];
        // wir ermitteln die MAC Adresse und speichern sie im RTC Memory
        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 ) {
            statinfo.mac[ii] = (uint8_t) mac[ii];
          }
          UpdateRtcMemory();
        }

        slaveFound = 1;
        //Nachdem der AP gefunden wurde können wir abbrechen
        break;
      }
    }
  }
  if (DEBUG) {
    if (slaveFound) {
      Serial.println("Funkschalter gefunden!");
    } else {
      Serial.println("Kein Funkschalter. Neuer Versuch.");
    }
  }

  // RAM freigeben
  WiFi.scanDelete();
}

void setup() {
  if (DEBUG) {
    Serial.begin(115200); Serial.println();
  }
  //WiFi initialisieren im Station Mode
  WiFi.mode(WIFI_STA); // Station mode for esp-now sensor node
  WiFi.begin();
  
  //lesen der Daten vom RTC Memory und auf Gültigkeit prüfen
  ESP.rtcUserMemoryRead(0, (uint32_t*) &statinfo, sizeof(statinfo));
  uint32_t crcOfData = calculateCRC32(((uint8_t*) &statinfo) + 4, sizeof(statinfo) - 4);
  if (statinfo.crc32 != crcOfData) {
    //Daten nicht gültig wir suchen einen AP
    ScanForSlave();
    if (DEBUG) {
      Serial.printf("Meine MAC: %s, ", WiFi.macAddress().c_str()); 
      Serial.printf("Ziel MAC: %02x%02x%02x%02x%02x%02x", statinfo.mac[0], statinfo.mac[1], statinfo.mac[2], statinfo.mac[3], statinfo.mac[4], statinfo.mac[5]); 
      Serial.printf(", Kanal: %i\n", WIFI_CHANNEL); 
    }
  }
  //wir versuchen ESP Now zu initialisieren
  if (esp_now_init() != 0) {
    if (DEBUG) Serial.println("*** ESP_Now init failed");
    ESP.restart();
  }
  //warten 10 ms
  delay(10);
  //ESP-NOW vorbereiten
  esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);
  esp_now_add_peer(statinfo.mac, ESP_NOW_ROLE_SLAVE, WIFI_CHANNEL, NULL, 0);
  //Sende Callback Funktion registrieren
  esp_now_register_send_cb([](uint8_t* mac, uint8_t sendStatus) {
    if (DEBUG) {
      Serial.print("send_cb, status = "); Serial.print(sendStatus); 
      Serial.print(", to mac: "); 
      char macString[50] = {0};
      sprintf(macString,"%02X:%02X:%02X:%02X:%02X:%02X", statinfo.mac[0], statinfo.mac[1], statinfo.mac[2], statinfo.mac[3], statinfo.mac[4], statinfo.mac[5]);
      Serial.println(macString);
    }
    callbackCalled = true;
  });

  callbackCalled = false;
  //Daten zum Senden bereiten
  DATEN_STRUKTUR data;
  if (statinfo.status == 0) {
    if (DEBUG) Serial.println("Relais ein");
    data.relais = 54321; //Kennung für Relais ein
    statinfo.status = 1;
  } else {
    if (DEBUG) Serial.println("Relais aus");
    data.relais = 12345; //Kennung für Relais aus
    statinfo.status = 0;
  }
  //neuen Relais-Status im RTC Memory merken
  UpdateRtcMemory();
  //Daten an Relais senden
  uint8_t bs[sizeof(data)];
  memcpy(bs, &data, sizeof(data));
  esp_now_send(NULL, bs, sizeof(data)); // NULL bedeutet qalle Peers
}

void loop() {
  //nachdem die Daten komplett gesendet wurden oder ein Timeout auftritt
  //wird der Tiefschlaf-Modus aktiviert.
  if (callbackCalled || (millis() > SEND_TIMEOUT)) {
    if (DEBUG) Serial.println("Sleep");
    delay(100);
    ESP.deepSleep(0); //Tiefschlaf für immer
  }
}




 

Letzter Artikel Uno R3 als Webserver mit Ethernet Shield

Kommentar

Gerald Lechner - Dezember 1, 2018

Hallo Norman. Das verwendete Relaismodul steuert das Relais über einen eigenen Controler Chip an, der über die Serielle Schnittstelle angesteuert wird.

byte RELAIS_OFF[] = {0xA0,0×01,0×01,0xa2};
byte RELAIS_ON[] = {0xA0,0×01,0×00,0xa1};

if (data.relais == 12345) { Serial.write(RELAIS_ON,sizeof(RELAIS_ON)); } if (data.relais == 54321) { Serial.write(RELAIS_OFF,sizeof(RELAIS_OFF)); }
Norman Rupp - Dezember 1, 2018

Hallo,

tolle Anleitung. Würde das gerne ausprobieren, habe aber noch eine Frage:
An dem ESP01, mit dem Relais, welcher GPIO wird da geschaltet?

Viele Grüße
Norman

Hinterlasse einen Kommentar

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

Erforderliche Angabe