In diesem Beitrag möchte ich dir zeigen, wie du eine Zugriffskontrolle mit einem Nummernfeld und einem ESP32 für einen Shelly bauen und programmieren kannst.
Den Shelly habe ich dir bereits in diversen Beiträgen vorgestellt, wie zum Beispiel.:
- Shelly Plus #1: Shelly Plus 1 Überblick
- Einsatzgebiete für den Shelly
- Shelly Plus #4: PIR Sensor am AddOn
Der Shelly verfügt über ein Relais, mit welchem wir Lasten bis zu 16A steuern können und wenn wir ein Schütz anklemmen, sogar noch größere. Mit diesem Relais können wir somit recht einfach ein Rolltor oder ein Schiebetor mit Motor steuern.
Was ist ein Schütz?
Ein Schütz, auch bekannt als Schaltschütz, dient zum Schalten von großen Lasten. Dabei wird mit einer kleinen Spannung eine große Last über einen elektromagnetischen Schalter gesteuert. Mithilfe dieses Bauteils können wir also mit einer kleinen Spannung einen großen Verbraucher schalten.
Möchtest du mehr über dieses Bauteil lesen, so empfehle ich dir den Beitrag von elektro4000.de.
Ziel des Projektes – Zugriffskontrolle per Nummernfeld am ESP32 für Shelly
Das Ziel dieses kleinen Projektes ist es, eine Schaltung aufzubauen, mit welcher wir über ein Nummernfeld und einem ESP32 den Befehl an ein Shelly zum Aktivieren / Deaktivieren senden können. Die Stern-Taste soll mit der Funktion letzte Stelle löschen und die Raute-Taste mit Ausführen belegt werden.
Die Eingabe soll dabei über ein LCD-Display ablesbar sein.
Benötigte Ressourcen für das Projekt
Wenn du das Projekt nachbauen möchtest, dann benötigst du:
- einen Shelly,
- einen ESP32,
- ein Nummernfeld,
- ein LCD-Display mit I2C Schnittstelle,
- diverse Breadboardkabel,
Der Shelly benötigt für den Betrieb eine Stromquelle von 12 V Gleichstrom oder 230 V Wechselstrom. Den ESP32 versorge ich in diesem Beispiel mit einem einfachen USB-Netzteil.
Aufbau der Schaltung – Nummernfeld am ESP32
Schließen wir zunächst das Nummernfeld und das LCD-Display an den ESP32 an.
Nachfolgend die Pinbelegungen am ESP32 für diese Schaltung:
Bauteil | ESP32 |
---|---|
3×4 Keypad | |
Pin 7 | GPIO12 |
Pin 6 | GPIO14 |
Pin 5 | GPIO27 |
Pin 4 | GPIO16 |
Pin 3 | GPIO17 |
Pin 2 | GPIO25 |
Pin 1 | GPIO26 |
LCD-Display | |
GND | GND |
VCC | 5 V |
SDA | GPIO21 |
SCL | GPIO22 |
LED, grün | GPIO19 |
LED, rot | GPIO18 |
Buzzer | GPIO23 |
Anschluss des Shelly
Der Shelly kann wie bereits erwähnt Verbraucher bis zu 16A schalten, das reicht im Hausgebrauch für die meisten Für diesen Aufbau verwende ich eine Lampe, welche im späteren Verlauf aktiviert / deaktiviert wird.
Die Lampe habe ich wie im Beitrag Shelly Plus #1: Shelly Plus 1 Überblick mit einer Steckdose dargestellt angeschlossen.
Relais per HTTP Request steuern
Das Relais lässt sich sehr einfach per HTTP Request steuern. Die Dokumentation zur Schnittstelle findest du auf der Seite https://shelly-api-docs.shelly.cloud/gen2/ComponentsAndServices/HTTP/.
Für das Absenden benötigen wir die IP-Adresse des Shellys diese findest du entweder in deinem Router oder in der Shelly App, wenn du das Gerät auswählst und dort die Geräteinformationen aufrufst.
Zum Aktivieren des Relais müssen wir nur den Befehl im Browser oder per Postman ausführen.
http://<IP-Adresse>/relay/0?turn=on
Wenn das Relais deaktiviert werden soll, so muss der Parameter „turn“ lediglich auf den Wert „off“ gestellt werden.
http://<IP-Adresse>/relay/0?turn=off
Als Rückgabe erhält man ein JSON:
{"ison": true, "has_timer":false, "timer_started_at": 0, "timer_duration": 0.00, "timer_remaining": 0.00, "source": "http"}
LCD-Display 16×2
Auf dem Display werden die verschiedenen Zustände und Meldungen angezeigt.
Im Fehlerfall soll zbsp. wenn der Shelly nicht erreichbar ist, die Meldung „Device not found“ angezeigt werden. Wenn die WiFi Verbindung verloren gegangen ist, dann soll „missing WiFi“ angezeigt werden.
Wenn eine gültige Pin eingegeben wurde, dann wird dem Benutzer „ACCESS GRANTED!“ angezeigt.
In allen anderen Fällen erscheint die Textzeile zum Eingeben einer Pin.
Programmieren in der Arduino IDE
Wie man die 3×4 Matrix am Arduino programmiert, habe ich dir bereits im Beitrag Arduino Lektion 64: 3×4 Matrix Tastatur erläutert, diesen Beitrag verwende ich als Grundlage für das Programm.
erzeugen einer Ausgabe am LCD-Display
Zunächst binden wir die Bibliothek LiquidCrystal mit dem Befehl include ein. Diese kannst du über den internen Bibliotheksverwalter (1) der Arduino IDE installieren, indem du zunächst nach „LiquidCrystal“ (2) suchst und dann die Schaltfläche „INSTALL“ (3) am Eintrag „LiquidCrystal by Arduino, Adafruit“ klickst. Wenn der Installationsprozess erfolgreich abgeschlossen ist, dann sollte der Text „INSTALLED“ (4) angezeigt werden.
//Einbinden der Bibliotheken //für das LCD-Display #include <Wire.h> #include <LiquidCrystal_I2C.h> //Es wird ein I2C Display mit 16 Zeichen, //und zwei Zeilen verwendet. LiquidCrystal_I2C lcd(0x27, 16, 2); void setup() { //initialisieren des LCD-Displays lcd.init(); lcd.backlight(); lcd.clear(); lcd.setCursor(0, 0); lcd.print("Shelly Secure v1"); lcd.setCursor(0, 1); lcd.print("PIN:"); } /** * Funktion wird fortlaufen ausgeführt. **/ void loop() { //bleibt leer }
lesen der Tasten am 3×4 Keypad
Für die Programmierung des 3×4 Keypad benötigen wir wie auch beim LCD-Display eine Bibliothek, welche wir ebenso über den Bibliotheksmanager installieren. Dazu suchen wir nach „Keypad“ (1) und wählen am Eintrag „Keypad by Mark Stanley, Alexander Brevig“ die Schaltfläche „INSTALL“ (2), wenn der Installationsprozess abgeschlossen ist, dann wird der Text „INSTALLED“ (3) angezeigt.
//Einbinden der Bibliotheken //für das 3x4 Keypad #include <Keypad.h> //Definieren des Keypads const byte COLS = 3; //3 Spalten const byte ROWS = 4; //4 Zeilen //Pins von Links nach Rechts! byte colPins[COLS] = { 12, 14, 27 }; byte rowPins[ROWS] = { 16, 17, 25, 26 }; //definieren wo welche Taste liegt. char KEYS[ROWS][COLS] = { { '#', '0', '*' }, { '9', '8', '7' }, { '6', '5', '4' }, { '3', '2', '1' } }; //Instanziieren eines Objektes vom Typ Keypad //mit den zuvor definierten Werten Keypad myKeypad = Keypad(makeKeymap(KEYS), rowPins, colPins, ROWS, COLS); void setup() { //begin der seriellen Kommunikation mit 115200 baud Serial.begin(115200); } /** * Funktion wird fortlaufen ausgeführt. **/ void loop() { //auslesen der gedrückten Taste char key = myKeypad.getKey(); //Wenn eine Taste betätigt ist, dann enthält die Variable key einen Wert if (key) { Serial.println(key); } }
Aufbau einer WiFi-Verbindung und absenden eines HTTP Requests
Die Bibliotheken für den ESP32 wurden bereits mit dem Boardtreiber installiert, daher müssen wir diese lediglich mit dem include Befehl einbinden.
//Einbinden der Bibliotheken //für die WiFi Verbindung des ESP32 #include <WiFi.h> #include <HTTPClient.h> //SSID & Passwort für das lokale WiFi #define WIFI_SSID "***" #define WIFI_PASSWORD "***" void setup() { //begin der seriellen Kommunikation mit 115200 baud Serial.begin(115200); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(200); } //Wenn die Verbindung erfolgreich aufgebaut wurde, //dann soll die IP-Adresse auf der seriellen Schnittstelle //ausgegeben werden. Serial.println(""); Serial.println("WiFi connected."); Serial.print("IP address: "); Serial.println(WiFi.localIP()); Serial.println(); } /** * Funktion wird fortlaufen ausgeführt. **/ void loop() { //bleibt leer }
Der komplette Code für die Zugriffskontrolle am ESP32 mit Nummernfeld für den Shelly
Hier nun der komplette Code mit Kommentaren. Am Ende des Beitrages findest du einen Link, wo du diesen einfach als ZIP-Datei herunterladen kannst.
//Einbinden der Bibliotheken //für das LCD-Display #include <Wire.h> #include <LiquidCrystal_I2C.h> //für das 3x4 Keypad #include <Keypad.h> //für die WiFi Verbindung des ESP32 #include <WiFi.h> #include <HTTPClient.h> //SSID & Passwort für das lokale WiFi #define WIFI_SSID "****" #define WIFI_PASSWORD "*****" //Pins der LEDs #define ledGruen 19 #define ledRot 18 //Pin des Buzzers #define buzzer 23 //Konstanten für die PWM Signalerzeugung. //Diese Werte werden benötigt damit ein //Ton am Buzzer wiedergegeben werden kann. const int CHANNEL = 0; const int FREQUENZ = 2000; const int RESOLUTIONBITS = 12; const int TONE_FREQ = 600; //Es wird ein I2C Display mit 16 Zeichen, //und zwei Zeilen verwendet. LiquidCrystal_I2C lcd(0x27, 16, 2); //Definieren des Keypads const byte COLS = 3; //3 Spalten const byte ROWS = 4; //4 Zeilen //Pins von Links nach Rechts! byte colPins[COLS] = { 12, 14, 27 }; byte rowPins[ROWS] = { 16, 17, 25, 26 }; //definieren wo welche Taste liegt. char KEYS[ROWS][COLS] = { { '#', '0', '*' }, { '9', '8', '7' }, { '6', '5', '4' }, { '3', '2', '1' } }; //Instanziieren eines Objektes vom Typ Keypad //mit den zuvor definierten Werten Keypad myKeypad = Keypad(makeKeymap(KEYS), rowPins, colPins, ROWS, COLS); //Index ab wo die Pineingabe startet. //Auf dem Display soll in Zeile 2 zunächst //das Wort "Pin:" stehen. int keyIndex = 4; //Index für die Anzahl der gedrückten Tasten int pinIndex = 0; //Auf dem Display sind lediglich 11 freie Stellen, //dieses bedeutet das eine Pin maximal 11 Stellen haben soll/kann. char pin[11] = {}; //Die Pin welche später geprüft werden soll. //Die Raute am Ende signalisiert das Ende der Pin. const char PIN[11] = { '1', '4', '6', '2', '#' }; //Feld zum speichern ob die Pin korrekt eingegeben wurde. bool accessGranted = false; //Die Adresse zum steuern des Relais. //Hier musst du deine IP-Adresse eintragen! //der Wert für den Key "turn" wird später im Code ergänzt. String shellyRelaisAddress = "http://192.168.178.101/relay/0?turn="; //Feld zum speichern des Relaisstatus bool relaisStatus = false; void setup() { //begin der seriellen Kommunikation mit 115200 baud Serial.begin(115200); //Definieren das die Pins der LEDs //und des Buzzers als Ausgang dient. pinMode(ledGruen, OUTPUT); pinMode(ledRot, OUTPUT); pinMode(buzzer, OUTPUT); //initial die rote LED aktivieren //und die grüne deaktivieren digitalWrite(ledRot, HIGH); digitalWrite(ledGruen, LOW); //konfigurieren des PWM Channels ledcSetup(CHANNEL, FREQUENZ, RESOLUTIONBITS); ledcAttachPin(buzzer, CHANNEL); //starten der WiFi-Verbindung initWiFi(); //initialisieren des LCD-Displays lcd.init(); lcd.backlight(); //initialisieren des Displays und der Felder init(); } /** * Funktion initialisiert die WiFi-Verbindung * Wenn die Verbindung aufgebaut wurde, dann wird * die IP-Adresse auf der seriellen Schnittstelle ausgegeben. **/ void initWiFi() { WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(200); } //Wenn die Verbindung erfolgreich aufgebaut wurde, //dann soll die IP-Adresse auf der seriellen Schnittstelle //ausgegeben werden. Serial.println(""); Serial.println("WiFi connected."); Serial.print("IP address: "); Serial.println(WiFi.localIP()); Serial.println(); } /** * Initialisiert das LCD-Display & die Felder **/ void init() { accessGranted = false; keyIndex = 4; pinIndex = 0; //zurücksetzen der eingegeben Zeichen pin[11] = {}; //löscht das LCD-Display lcd.clear(); reset(); digitalWrite(ledRot, HIGH); digitalWrite(ledGruen, LOW); } /** * Funktion zeigt auf dem LCD-Display * in Zeile 1 den Text "Shelly Secure v1" an * in Zeile 2 den Text "PIN:" gefolgt von * den bisher eingegebenen Zeichen an. **/ void reset() { lcd.setCursor(0, 0); lcd.print("Shelly Secure v1"); lcd.setCursor(0, 1); lcd.print("PIN:"); //Schleife von 0 bis pinIndex, //wobei pinIndex maximal 11 Stellen haben kann. for (int i = 0; i < pinIndex; i++) { //den aktuellen Cursor um eins nach links schieben //das Zeichen aus dem Array an die Position schreiben lcd.setCursor(i + 4, 1); lcd.print(pin[i]); } } /** * Wenn die eingegebene Pin ok ist dann wird, * das Display gelöscht und * die Textzeile "ACCESS GRANTED!" angezeigt. **/ void executeAccessGranted() { lcd.clear(); lcd.setCursor(0, 0); lcd.print("Shelly Secure v1"); lcd.setCursor(0, 1); lcd.print("ACCESS GRANTED!"); //setzen des Feldes auf TRUE accessGranted = true; //aktivieren/deaktivieren des Relais setShellyRelaisStatus(); } /** * Wenn die WiFi-Verbindung erfolgreich aufgebaut wurde, * dann wird der Status des Relais umgekehrt * aus true wird false / aus false wird true * und je nach Wert dann * der Parameterwert "on" oder "off" angehängt. * Die URL sowie der HTTP-ResponseCode wird * für Debugausgaben in der seriellen Schnittstelle angezeigt. **/ int setShellyRelaisStatus() { if (WiFi.status() == WL_CONNECTED) { String command = shellyRelaisAddress; relaisStatus = !relaisStatus; if (relaisStatus) { command += "on"; digitalWrite(ledGruen, HIGH); digitalWrite(ledRot, LOW); } else { command += "off"; init(); } Serial.print("URL:"); Serial.println(command); HTTPClient http; http.setTimeout(1000); http.begin(command.c_str()); int httpResponseCode = http.GET(); Serial.print("HTTP Code:"); Serial.println(httpResponseCode); http.end(); //Ein HTTP-ResponseCode ungleich als 200 bedeutet ein Fehler. //In diesem Fall wird es mit hoher Wahrscheinlichkeit sein das, //der Shelly nicht erreichbar ist. if (httpResponseCode != 200) { //Ausgeben der Meldung auf dem Display printMessage("Device not found"); } } else { //Wenn die WiFi-Verbindung nicht aufgebaut werden konnte, dann //soll eine entsprechende Fehlermeldung ausgegeben werden. printMessage("missing WiFi"); } } /** * Ausgabe einer Fehlermeldung auf dem LCD-Display. **/ void printMessage(String msg) { lcd.clear(); lcd.setCursor(0, 0); lcd.print("Shelly Secure v1"); lcd.setCursor(0, 1); lcd.print(msg); } /** * Wenn eine Taste ungleich Stern/Raute betätigt wird, * dann soll diese auf dem Display angezeigt werden und * an dem Index im Array gespeichert werden. **/ void printKey(char key) { if (accessGranted) { return; } lcd.setCursor(keyIndex, 1); lcd.print(key); pin[pinIndex] = key; //Indexe um eins erhöhen keyIndex++; pinIndex++; } /** * Ausgabe eines Tones auf dem Piezo Buzzer. **/ void playTone() { ledcWriteTone(CHANNEL, TONE_FREQ); //eine kleine Pause von 125 Millisekunden delay(125); ledcWriteTone(CHANNEL, 0); } /** * Die Stern-Taste dient zum löschen der letzten Eingabe. * Wenn der Index jedoch gleich 4 ist, dann soll die Funktion * vorzeitig verlassen werden. **/ void removeLastNumber() { if (keyIndex == 4) { return; } //verringern der Indexe um eins pinIndex--; keyIndex--; //löschen des Displays lcd.clear(); //initialisieren des Displays //es werden alle zuvor eingegeben Zeichen angezeigt reset(); } /** * Die Raute-Taste führt den Check der eingegebenen Pin aus. * Wenn jedoch bereits erfolgreich eine Pin eingegeben wurde, führt * die Funktion ein Reset durch und verlässt die Funktion vorzeitig. * Andernfalls wird die eingegebene Pin mit der hinterlegten Pin verglichen. * Wenn die Prüfung erfolgreich war dann soll die rote LED deaktiviert werden und die * grüne LED blinken. Danach wird die Funktion executeAccessGranted ausgeführt. * Wenn die Prüfung nicht erfolgreich war dann soll die rote LED blinken und danach * die rote LED aktiviert sowie die grüne LED deaktiviert werden. **/ void execute() { if (accessGranted) { init(); return; } if (checkPin()) { digitalWrite(ledRot, LOW); blinkLed(ledGruen, 75); executeAccessGranted(); } else { blinkLed(ledRot, 75); digitalWrite(ledRot, HIGH); digitalWrite(ledGruen, LOW); } } /** * Funktion zum blinken der LED und ausgeben eines Tones. **/ void blinkLed(int ledPin, int pause) { for (int i = 0; i < 4; i++) { digitalWrite(ledPin, HIGH); playTone(); delay(pause); digitalWrite(ledPin, LOW); playTone(); delay(pause); } } /** * Prüfen der eingegebenen Pin gegen die hinterlegte Pin. **/ bool checkPin() { bool result = true; for (int i = 0; i < 11; i++) { //das Rautesymbol signalisiert das Ende der Pin if (PIN[i] == '#') { return result; } //Wenn die Stelle in den beiden Arrays sich unterscheiden //dann ist die Pin nicht korrekt und es wird false zurückgeliefert if (pin[i] != PIN[i]) { return false; } else { result = true; } } return result; } /** * Funktion wird fortlaufen ausgeführt. **/ void loop() { //auslesen der gedrückten Taste char key = myKeypad.getKey(); //Wenn eine Taste betätigt ist, dann enthält die Variable key einen Wert if (key) { //einen Ton ausgeben playTone(); switch (key) { //Wenn die Stern-Taste betätigt wurde, dann... case '*': removeLastNumber(); break; //Wenn die Raute-Taste betätigt wurde, dann... case '#': execute(); break; //andernfalls, dann... default: printKey(key); break; } } }
Super Artikel. Gut verständlich und mit minimalem Aufwand! 🙂